			<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
  	<title>JeeSite 4.0_V1.0</title>
  	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	<style>
		        body {
            color: #666666;
            font-family: 微软雅黑,Verdana,sans-serif,宋体;
            font-size: 14px;
            margin: 0;
            padding: 0;
        }
        a {
            color: #999999;
            text-decoration: none;
        }
        a:hover {
            color: #16B28F;
        }
        .sin_navi ul {
            list-style: none outside none;
            margin: 0;
            padding: 0 0 0 10px;
        }
        .sin_navi ul li {
            line-height: 30px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        li {
            line-height: 24px;
        }
        table {
            border-left: 1px solid #CCCCCC;
            border-top: 1px solid #CCCCCC;
            margin: 5px 10px;
            text-align: left;
            width: 678px;
        }
        table th {
            background-color: #EEEEEE;
            border-bottom: 1px solid #CCCCCC;
            border-right: 1px solid #CCCCCC;
            padding: 10px;
        }
        table td {
            border-bottom: 1px solid #CCCCCC;
            border-right: 1px solid #CCCCCC;
        }
        p {
            line-height: 24px;
            margin: 10px;
        }
        h1, h2, h3, h4, h5, h6 {
            color: #111111;
            font-weight: normal;
            margin: 0;
            padding: 0;
        }
        h1 {
            font-size: 26px;
        }
        h2 {
            border-bottom: 1px dashed #CCCCCC;
            font-size: 24px;
            padding: 10px 0;
        }
        h3 {
            font-size: 20px;
            padding: 8px 0;
        }
        h4 {
            font-size: 18px;
            margin-left: 10px;
            padding: 5px 0;
        }
        h5 {
            font-size: 16px;
            margin-left: 10px;
            padding: 5px 0;
        }
        h6 {
            font-size: 16px;
            margin-left: 10px;
            padding: 5px 0;
        }
        blockquote {
            background: url("../../../img/blockquote.gif") no-repeat scroll left top #F4F5F7;
            margin: 5px 10px;
            padding: 10px 10px 10px 30px;
        }
        pre {
            background: none repeat scroll 0 0 #EEEEEE;
            margin: 5px 10px;
            padding: 10px;
        }
        .sin_callout {
            margin: 5px 10px;
        }
        .sin_header {
            background-color: #F7F7F7;
            border-bottom: 1px solid #CCCCCC;
            height: 80px;
            width: 100%;
        }
        .sin_title {
            margin: 0 auto;
            width: 1000px;
        }
        .sin_title_text {
            color: #000000;
            float: left;
            line-height: 80px;
        }
        .sin_version {
            color: #999999;
            float: left;
            font-size: 14px;
            padding-top: 25px;
            margin-left: 10px;
        }
        .sin_main {
            margin: 0 auto;
            width: 1000px;
        }
        .sin_navi {
            float: left;
            margin-bottom: 20px;
            overflow-y: scroll;
            padding: 0;
            position: relative;
            width: 279px;
            z-index: 1;
        }
        .sin_navi_nomove {
            position: fixed;
            top: 0;
        }
        .sin_navi_ca a {
            color: #333333;
        }
        .sin_navi_ca a:hover {
            color: #16B28F;
        }
        .sin_navi_current {
            border-right: 3px solid #16B28F;
        }
        .sin_navi_current a {
            color: #16B28F;
        }
        .sin_content {
            background-color: #FFFFFF;
            border-left: 1px solid #CCCCCC;
            float: left;
            margin-left: -20px;
            padding: 10px 20px;
            position: relative;
            width: 699px;
            z-index: 2;
        }
        .sin_content_mar {
            margin-left: 259px;
        }
        .sin_footer {
            border-top: 1px solid #CCCCCC;
            color: #999999;
            height: 80px;
            line-height: 80px;
            margin: 0 auto;
            text-align: center;
        }
        .sin_info {
            background: none repeat scroll 0 0 #FFFFCC;
            border: 1px solid #D5D4D4;
            border-radius: 4px 4px 4px 4px;
            color: #999999;
            margin: 10px 0;
            padding: 14px;
        }
        .sin_link_logo {
            color: #16B28F;
            font-size: 12px;
            margin-left: 10px;
        }
        .sin_text {
            margin-bottom: 10px;
        }
        .d_callout {
            margin-bottom: 10px;
        }
        .d_callout_info {
            background: none repeat scroll 0 0 #F4F8FA;
            border-left: 4px solid #5BC0DE;
            padding: 10px;
        }
        .d_callout_warn {
            background: none repeat scroll 0 0 #FCF8F2;
            border-left: 4px solid #F0AD4E;
            padding: 10px;
        }
        .d_callout_danger {
            background: none repeat scroll 0 0 #FDF7F7;
            border-left: 4px solid #D9534F;
            padding: 10px;
        }
        .d_callout_success {
            background: none repeat scroll 0 0 #F3F8F3;
            border-left: 4px solid #50AF51;
            padding: 10px;
        }
        .d_callout input {
            background-color: rgba(0, 0, 0, 0);
            font-size: 15px;
            margin-bottom: 5px;
            padding: 10px 5px 0 10px;
        }
        .d_callout textarea {
            background-color: rgba(0, 0, 0, 0);
        }
        .d_callout_icons a {
            color: #AAAAAA;
            margin-left: 10px;
        }
        a.d_callout_icon_info {
            color: #5BC0DE;
        }
        a.d_callout_icon_warn {
            color: #F0AD4E;
        }
        a.d_callout_icon_danger {
            color: #D9534F;
        }
        a.d_callout_icon_success {
            color: #50AF51;
        }
        .clear {
            clear: both;
        }

	</style>
</head>
<body>
	<div class="sin_header">
		<div class="sin_title">
			<h1 class="sin_title_text">JeeSite 4.0</h1>
			<span class="sin_version">V1.0</span>
			<div class="clear"></div>
		</div>
		<div id="top_end"></div>
	</div>
	<div class="sin_main">
		<div class="sin_navi" id="sin_navi_id">
			<ul style="margin: 20px 0;">
				    																					<li class="sin_navi_ca" style="width: 246px;" id="navi_category_54968">
								<a href="#category_54968">
									快速了解
								</a>
							</li>
										<ul>
																								<li style="width:236px" id="navi_text_224722">
						<a href="#text_224722">
							简介
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_281645">
						<a href="#text_281645">
							新特性
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_273599">
						<a href="#text_273599">
							技术选型
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_270187">
						<a href="#text_270187">
							功能菜单介绍
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_273830">
						<a href="#text_273830">
							V4.0.2 更新日志
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_66420">
								<a href="#category_66420">
									开发手册
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_267354">
						<a href="#text_267354">
							开发环境部署运行
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_267351">
						<a href="#text_267351">
							数据持久层开发手册
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_267352">
						<a href="#text_267352">
							业务处理层开发手册
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_267353">
						<a href="#text_267353">
							前端页面视图开发手册
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_54969">
								<a href="#category_54969">
									开发技巧
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_267355">
						<a href="#text_267355">
							替换默认视图，自定义主题
						</a>	
					</li>
																										<li style="width:236px" id="navi_text_270527">
						<a href="#text_270527">
							前后分离、接口调用、移动端API
						</a>	
					</li>
											</ul>
																									<li class="sin_navi_ca" style="width: 246px;" id="navi_category_64919">
								<a href="#category_64919">
									服务支持
								</a>
							</li>
										<ul>
																			<li style="width:236px" id="navi_text_267685">
						<a href="#text_267685">
							技术服务与支持
						</a>	
					</li>
											</ul>
																			</ul>
		</div>
		<div class="sin_content" id="sin_content_id">
																					<h2 id="category_54968">快速了解</h2>
																								<h3 id="text_224722" class="sin_target">简介</h3>
<div class="sin_text">
    					<ul> 
 <li>JeeSite 官网地址：<a href="http://jeesite.com" rel="nofollow">http://jeesite.com</a></li> 
 <li>JeeSite 在线文档：<a href="http://jeesite4.mydoc.io" rel="nofollow">http://jeesite4.mydoc.io</a></li> 
 <li>JeeSite 源码下载：<a href="https://gitee.com/thinkgem/jeesite4" rel="nofollow">https://gitee.com/thinkgem/jeesite4</a></li> 
 <li>JeeSite 在线提问：<a href="https://gitee.com/thinkgem/jeesite4/issues?issue_state_id=all&amp;issue_type_id=all" rel="nofollow">https://gitee.com/thinkgem/jeesite4/issues?issue_state_id=all&amp;issue_type_id=all</a></li> 
 <li>JeeSite 演示地址：<a href="http://demo.jeesite.net" rel="nofollow">http://demo.jeesite.net</a></li> 
 <li>JeeSite 开源社区：<a href="http://www.jeesite.net" rel="nofollow">http://www.jeesite.net</a></li> 
 <li>ThinkGem 博客：<a href="http://my.oschina.net/thinkgem/blog" rel="nofollow">http://my.oschina.net/thinkgem/blog</a></li> 
</ul>
<p>JeeSite 是一个 Java EE 企业级快速开发平台，基于经典技术组合（Spring Boot、Spring MVC、Apache Shiro、MyBatis、Beetl、Bootstrap、AdminLTE），在线代码生成功能，包括核心模块如：组织机构、角色用户、菜单及按钮授权、数据权限、系统参数、内容管理、工作流等。采用松耦合设计；界面无刷新，一键换肤；众多账号安全设置，密码策略；在线定时任务配置；支持集群，支持SAAS；支持多数据源。</p>
<p>JeeSite 快速开发平台的主要目的是能够让初级的研发人员快速的开发出复杂的业务功能，让开发者注重专注业务，其余有平台来封装技术细节，降低技术难度，从而节省人力成本，缩短项目周期，提高软件安全质量。</p>
<p>JeeSite 自开源以来已被广大爱好者用到了企业、政府、医疗、金融、互联网等各个领域中，JeeSite 依架构简单精良、易于扩展、大众思维的设计模式，深入开发者的内心，并得到一致好评，于<a href="http://www.oschina.net/project/top_cn_2016?sort=1" rel="nofollow">2016</a>和<a href="http://www.oschina.net/project/top_cn_2017?sort=1" rel="nofollow">2017</a>连续两年获得开源中国《最受欢迎中国开源软件》奖杯，期间也帮助了不少刚毕业的大学生作为入门教材，快速的去实践。</p>
<p>现在 JeeSite 4.0 来了，4.0的升级，作者结合了多年总结和经验，以及各方面的应用案例，对架构完成了一次全部重构，也纳入很多新的思想。不管是从开发者模式、底层架构、逻辑处理还是到用户界面，用户交互体验上都有很大的进步，在不忘学习成本、提高开发效率的情况下，安全方面也做和很多工作，包括：身份认证、密码策略、安全审计、日志收集。</p>
			</div>

																						<h3 id="text_281645" class="sin_target">新特性</h3>
<div class="sin_text">
    					<h1>底层架构优化</h1> 
<ol> 
 <li>Maven多项目依赖，模块及插件分项目，尽量松耦合，方便模块升级、增减模块。</li> 
 <li>模块化数据库自动升级程序，当模块升级代码需要更新时，自动对应版本执行SQL更新。</li> 
 <li>授权模块，支持CAS单点登录，简单properties配置即可，不用再写很多的xml。</li> 
 <li>支持多数据源，分布式事务，简单properties配置即可实现，为了安全性，不提供界面维护数据源。</li> 
 <li>数据表主键优化，如分类科目表，采用有意义的主键方式，让客户去自定义，减少后期运维维护成本。</li> 
 <li>实体@Table注解配置</li> 
</ol>
<p>a. 自动生成增删改通用SQL，不需要在mapper.xml里写重复又费时的SQL了，减少开发和后期维护成本。<br> b. 这里所有定义@Column均放到类头，而不是分布到各个属性或方法上，这是如下原因：<br> c. 一是，放到表头的好处是，可一览熟知该实体类对应的物理表结构是什么样，开发者思维从物理表结构到对象的映射转换，都是基于物理表结构的，@Column指定物理字段名，而不是指定类上的属性名，也是这个原因；<br> d. 二是，生成的SQL和查询条件，列的排列顺序，可方便核查定义，优化查询；<br> e. 三是，方便@JoinTable关联表和其它扩展信息的设置，如果分布到类的属性上就不太好管理列。</p> 
<ol> 
 <li>用户数据权限优化，不仅仅是角色，颗粒度细化到每个人员的身上，可自定义第三方数据权限，不仅仅是部门和公司，你可以扩展，如：地区，栏目分类，商品分类。</li> 
 <li>支持SAAS多租户模式，每个租户数据通过corp_code字段进行分离，数据互不干扰。</li> 
 <li>缓存EhCache统一管理，支持快速切换为Redis缓存，集群Session缓存共享。</li> 
</ol>
<h1>安全方面优化</h1> 
<ol> 
 <li>原有JeeSite1.2安全选项及安全考虑保留。</li> 
 <li>配置文件数据库密码及其它安全密钥自动加密。</li> 
 <li>所有请求参数获取，均通过XSS跨站脚本过滤方法。</li> 
 <li>乐观锁简单实现，必要情况下使用，提高数据安全性。</li> 
 <li>通过properties简单配置，限制是否允许JS跨域操作。</li> 
 <li>身份认证</li> 
</ol>
<p>a. 登录失败多少次后显示验证码<br> b. 登录失败多少次后锁定账号及锁定账号时间<br> c. 登录账号密码可加密后再提交后台<br> d. 同设备是否允许账号多地登录</p> 
<ol> 
 <li>密码策略</li> 
</ol>
<p>a. 初始密码修改策略，提醒或强制用户修改初始密码。<br> b. 账号密码修改策略，多长时间内未修改，则提醒或强制修改密码。<br> c. 账号密码安全等级限制策略，很弱密码，弱密码，安全密码，很安全密码</p> 
<ol> 
 <li>安全审计（后期）</li> 
</ol>
<p>a. 查询未修改初始密码的账号，使用简单密码的，定期未修改的，长期未登录的。<br> b. 权限审计，按登录账号查询菜单和权限，按菜单和权限查询登录账号</p>
<h1>用户界面优化</h1> 
<ol> 
 <li>平面化界面设计，精细，更加美观、高端、大气、上档次。</li> 
 <li>支持手机或平板访问，响应式样式，根据屏幕分辨率自适应控件布局。</li> 
 <li>无刷新设计，除了进入功能页面和新页面，其它情况下全部采用Ajax交互，优化体验和性能。</li> 
 <li>支持一件换肤，只需在properties里修改下主题名称即可快速切换整个UI的风格，不仅仅是色调和样式，布局也可改变。支持自定义扩展项目独有的主题样式风格。</li> 
 <li>优化用户功能操作，大众思维模式，功能清晰，更加贴切和友好。</li> 
 <li>国际化支持</li> 
</ol>
<h1>前端开发优化</h1> 
<ol> 
 <li>采用当前比较流行的Beetl模板引擎，它的优点很多，大家可自行查阅看看，这里不多说了。</li> 
 <li>封装Beetl UI通用组件，简单实现基本表单控件、树选择，列表选择，文件上传，等等很多，总之是简化开发。</li> 
 <li>数据表格jqGrid组件封装，自动完成分页、排序、列宽、多表头、子表、编辑表、等。</li> 
</ol>
<h1>功能及组件优化</h1> 
<ol> 
 <li>工具类Utils封装优化，应有尽有，包分类层次分明，独立工具类项目。</li> 
 <li>强大的Excel导出导入工具封装，支持大数据量，注解定义，简单配置即可实现。</li> 
 <li>Job作业调度,界面化在线管理，可新增，编辑、删除、暂停、恢复、运行一次等操作</li> 
 <li>在线查询在线人员，强踢在线账号。</li> 
 <li>代码生成工具操作简化及优化。</li> 
</ol>
			</div>

																						<h3 id="text_273599" class="sin_target">技术选型</h3>
<div class="sin_text">
    					<h1>环境</h1> 
<ul> 
 <li>Java EE 6</li> 
 <li>Servlet 3.0</li> 
 <li>Apache Maven 3</li> 
</ul>
<h1>主框架</h1> 
<ul> 
 <li>Spring Boot 1.5</li> 
 <li>Spring Framework 4.3</li> 
 <li>Apache Shiro 1.4</li> 
</ul>
<h1>持久层</h1> 
<ul> 
 <li>Apache MyBatis 3.4</li> 
 <li>Hibernate Validation 5.3</li> 
 <li>Alibaba Druid 1.1</li> 
</ul>
<h1>视图层</h1> 
<ul> 
 <li>Spring MVC 4.3</li> 
 <li>Beetl 2.7 替换JSP</li> 
</ul>
<h3>CSS框架</h3> 
<ul> 
 <li>Bootstrap 3.3</li> 
 <li>AdminLTE 2.4</li> 
</ul>
<h3>JS框架及组件</h3> 
<ul> 
 <li>jquery 1.11</li> 
 <li>layer 3.0</li> 
 <li>zTree 3.5</li> 
 <li>jqGrid 4.7</li> 
 <li>jquery validation</li> 
 <li>My97DatePicker</li> 
 <li>webuploader</li> 
 <li>wdScrollTab</li> 
 <li>ueditor</li> 
 <li>toastr</li> 
</ul>
<h1>工具组件</h1> 
<ul> 
 <li>Logback 1.1</li> 
 <li>Apache Commons 
  <ul> 
   <li>lang3</li> 
   <li>io</li> 
   <li>codec</li> 
   <li>fileupload</li> 
   <li>banutils</li> 
   <li>email</li> 
  </ul></li> 
 <li>Jackson 2.8</li> 
 <li>POI / Office 3.14</li> 
 <li>Quartz 2.2</li> 
 <li>Lucene 5.3</li> 
 <li>FST 2.5</li> 
</ul>
<h1>数据缓存</h1> 
<ul> 
 <li>EhCache 2.10</li> 
 <li>Redis/Jedis 2.9</li> 
</ul>
<h1>数据库支持</h1> 
<ul> 
 <li>MySQL</li> 
 <li>Oracle</li> 
 <li>PostgreSQL</li> 
 <li>Sql Server</li> 
 <li>其它</li> 
</ul>
			</div>

																						<h3 id="text_270187" class="sin_target">功能菜单介绍</h3>
<div class="sin_text">
    					<h1>组织管理</h1>
<h2>用户管理</h2>
<p>用户管理是能够登录平台的账号集合，即登录用户。其最基本的属性为用户名和密码。登录用户类型分为内部员工、往来单位用户、会员用户，往来单位又分为供应商、客户、网点、院校、科研院所等。</p>
<p>主要功能包括：用户的维护、查询、授权角色、授权数据权限</p>
<p><strong>数据权限包括：</strong>拥有的权限和管理的权限；用户管理的数据权限被定义为管理的权限。管理的数据权限你需要在二级管理员里进行配置。</p>
<p>有这样的区分是因为，某用户，例如人事经理，拥有该部门下的新增和修改用户的权限，但不一定有该部门业务数据的查看权限。</p>
<h2>机构管理</h2>
<p>机构管理是指组织机构也是部门，采用多级组织管理维护，无限层级，支持全国应用。</p>
<h2>公司管理</h2>
<p>公司管理，包含以下几种场景：</p> 
<ol> 
 <li>在财务总账中具有 “帐套”的概念；</li> 
 <li>在费用预算系统中具有 “出账公司”的概念；</li> 
 <li>在HR系统中具有 “合同公司” 的概念；</li> 
 <li>在供应链中具有“交易公司”的概念，采用多级公司管理维护。</li> 
</ol>
<h2>岗位管理</h2>
<p>岗位管理，是组织架构下的精细岗位划分，是业务流程控制、业绩考核、预警体系的基础，不同的机构、部门下的同一职务，则是不同的岗位。内部员工的属性。</p>
<p>主要是面向业务管理，而角色主要是针对权限功能。</p>
<h1>权限管理</h1>
<h2>角色管理</h2>
<p>角色维护，是系统功能权限设置的基础，相当于权限分组，所有用户对应到相应权限角色，便具有该权限角色所赋予的所有菜单权限和操作权限。</p>
<p>主要功能包括：角色维护、授权功能菜单、授权数据权限、快速分配用户</p>
<h2>二级管理员</h2>
<p>二级管理员，是由系统管理员指定的，可以分担系统管理员的工作，可以管理用户、分配菜单权限和操作权限一种特殊角色，但它仅具备系统管理员指定范围的管理数据。</p>
<p>主要功能包括：二级管理员维护、设置可管理的部门、可管理的公司、可管理的角色</p>
<h2>系统管理员</h2>
<p>系统管理员为客户方使用的管理员，主要用于一些基础数据配置，如机构、用户、权限、用户字典等，默认账号为admin。</p>
<p>如果用超级管理员system登录，就这里可进行新增集团管理员（多租户）用户，新增的集团管理员，新增的集团管理员只可以管理自己领域内的数据（如：部门、公司、用户、自定义的字典数据等）。</p>
<p>注意：超级管理员system主要为开发者使用的最高级别管理员，主要用于开发和调试，有些修改会直接影响系统的正常运行。</p>
<p>主要功能包括：系统管理员维护、新增集团账号、新增管理员账号</p>
<h1>系统设置</h1>
<h2>菜单管理</h2>
<p>菜单管理主要用于配置系统菜单和操作权限。菜单即系统的功能菜单项，操作权限是属于菜单权限的子项，也就是具体的一个操作或按钮，例如：某一个菜单是“销售订单”，其对应的权限可能包括“增加”、“修改”、“删除”、“审核”、“作废”等一系列的权限。</p>
<p><strong>菜单权重</strong>是指，什么样的用户或管理员可以操作或访问什么级别的菜单，对菜单的权重级别进行划分，比如：比较重要敏感的菜单，只有管理员才可以拥有。如：超级管理员可以访问二级管理员、系统管理员、超级管理员权重的菜单，但不允许访问默认权限（业务菜单）；系统管理员可以访问超级管理员指定给他的系统管理员及以上权限的部分菜单；二级管理员可以访问超级或系统管理员指定给他的二级管理员及以上的部分菜单；普通用户只能访问管理员指定给他的默认权重的菜单。</p>
<h2>模块管理</h2>
<p>模块管理是针对系统功能进行模块划分，可对某个模块进行停用和启用。你可以通过API获取到模块的状态。菜单是和模块挂钩的，停用和启用后，对应菜单也同步和停用和启用。</p>
<p>系统启动时会自动检测模块是否安装，自动检测模块版本是否需要升级数据库，如果有数据库升级脚本，则系统自动执行。</p>
<p>若你新增了自定义的模块，指定的“主类全名”必须是一个存在的类，否则会提示“未安装”，并启动后会自动更新关联的菜单为“停用”状态。</p>
<h2>参数设置</h2>
<p>参数设置是提供开发人员、实施人员的动态系统配置参数，修改参数不需要去后台修改yml文件，也无需重启服务器即可生效，这里的配置参数开发人员可以通过通用的API进行调用，和获取yml里的参数API是一致的，参数值的读取顺序是：JVM中设置的参数 –&gt; jeesite.yml –&gt; application.yml –&gt; 本参数设置中的参数。</p>
<h2>字典管理</h2>
<p>字典管理是用来维护数据类型的数据，如下拉框、单选按钮、复选框、树选择的数据，方便系统管理员维护，如果客户要求增加或</p>
<p>主要功能包括：字典分类管理、字典数据管理</p>
<h2>行政区划</h2>
<p>行政区划多级字典的维护，主要包括：国家、省、市、县。</p>
<p>点击新建或编辑按钮，进入编辑区域界面，点击保存进行存储行政区划</p>
<h2>国际化管理</h2>
<p>主要用来维护动态数据的多语言译文管理，动态数据如：字典数据、菜单数据等。</p>
<h1>系统监控</h1>
<h2>访问日志</h2>
<p>系统留痕的一个日志记录，可记录：接入日志、修改日志、查询日志、登录登出</p>
<h2>数据监控</h2>
<p>监控数据源信息、SQL执行效能统计、URI访问统计</p>
<h2>缓存监控</h2>
<p>对EhCache的缓存查询，查看、清理等操作</p>
<h2>JVM监控</h2>
<p>显示当前JVM的状态、占用资源情况</p>
<h2>服务器监控</h2>
<p>监控服务器硬件资源使用情况</p>
<h2>作业监控</h2>
<p>对任务调度计划动态维护，支持集群情况自动调整，调度日志查询，Cron表达式界面化配置，Spring Bean调用、静态方法调用、启用停用、运行一次等功能</p>
<h2>在线用户</h2>
<p>查询活动用户（3分钟之内访问的用户），查询所有在线，查询游客用户（未登录的用），强制踢出在线用户会话</p>
<h2>安全审计</h2>
<p>密码审计：未修改初始密码、使用简单密码、定期未修改密码、长期未登录、以上全部</p>
<p>权限审计：按权限查询用户、按用户查询权限</p>
			</div>

																						<h3 id="text_273830" class="sin_target">V4.0.2 更新日志</h3>
<div class="sin_text">
    					<h1>V4.0.2 2018-4-22</h1>
<h2>新增</h2> 
<ul> 
 <li>新增：支持分布式事务，多数据源下事务支持</li> 
 <li>新增：支持MyBatisDao注解指定数据源名称，Dao层动态切换数据源</li> 
 <li>新增：国际化底层框架、通用组件、机构管理功能和代码生成模板</li> 
 <li>新增：Linux下执行的Shell脚本，并优化startup脚本</li> 
 <li>新增：@JoinTable 的 attrName 支持 this 当前类</li> 
 <li>新增：Redis 增加 clientName 设置参数</li> 
 <li>新增：附件列表增加上传人和时间回显</li> 
</ul>
<h2>优化</h2> 
<ul> 
 <li>优化：初始化SQL脚本优化，主要针对mysql脚本；修复创建索引警告；</li> 
 <li>优化：将timestamp替换为datetime类型，杜绝MySql下创建时间更新问题，时间不能为空问题。</li> 
 <li>优化：设置SpringBoot的上传文件大小限制与file.maxFileSize设置统一</li> 
 <li>优化：ExcelExport工具类接口，更方便的支持多Sheet。</li> 
 <li>优化：角色管理增加状态条件，优化查询</li> 
</ul>
<h2>修正</h2> 
<ul> 
 <li>修正：jquery validate remote 中文乱码导致验证失败问题</li> 
 <li>修正：因加载过早EmpUserService类事务失效问题</li> 
 <li>修正：Linux下找不到程序包 java.lang 问题</li> 
 <li>修正：代码生成选择多个验证条件的时候报错；</li> 
</ul>
<h1>V4.0.1 2018-3-31</h1>
<h2>新增</h2> 
<ul> 
 <li>新增：国际化登录相关和错误页面信息（中文、英文）</li> 
 <li>新增：数据表格实例，多表头、行分组、合计行、小计行、编辑表格，实例</li> 
 <li>新增：表单组件实例，栅格布局（一列、两列、三列、四列），组件应用实例</li> 
 <li>新增：日志查询，表增加执行时间记录字段，分析页面的执行效能</li> 
 <li>新增：CookieUtils.getCookie() 增加 path 参数</li> 
 <li>新增：支持Oracle、SqlServer、PostgreSQL数据库</li> 
 <li>新增：zTree 增加扩展方法 expandNodeByLevel 方便根据层级展开节点</li> 
 <li>新增：接口API，获取用户权限信息：/a/authInfo；获取用户菜单信息：/a/menuTree</li> 
</ul>
<h2>优化</h2> 
<ul> 
 <li>优化：Spring Boot 1.5.9 升级 1.5.10</li> 
 <li>优化：错误页面优化、升级 beetl 2.7.26</li> 
 <li>优化：手机端API接口测试优化，支持验证码登录</li> 
 <li>优化：解压war包后可直接执行startup.bat运行项目</li> 
 <li>优化：一些体验和性能优化</li> 
</ul>
<h2>修正</h2> 
<ul> 
 <li>修正：DataGrid冻结行的时候超连接未绑定btnList事件问题</li> 
 <li>修正：Excel导入导出中的DictUtils反射方法参数顺序不对问题 #IINDU</li> 
</ul>
			</div>

																														<h2 id="category_66420">开发手册</h2>
																								<h3 id="text_267354" class="sin_target">开发环境部署运行</h3>
<div class="sin_text">
    					<h1>环境要求</h1>
<p>1、Java SDK 1.8 <a href="http://www.oracle.com/technetwork/cn/java/javase/downloads/jdk8-downloads-2133151-zhs.html" rel="nofollow">下载</a></p>
<p>2、Eclipse IDE for Java EE Mars 2 (4.5.2) <a href="https://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/mars2" rel="nofollow">下载</a>（依Eclipse举例，IDEA雷同。）</p>
<p>3、Apache Maven 3.3+ <a href="https://maven.apache.org/download.cgi" rel="nofollow">下载</a></p>
<p>4、MySql 5.7+ <a href="https://dev.mysql.com/downloads/windows/installer/5.7.html" rel="nofollow">下载</a></p>
<h1>导入到Eclipse</h1>
<p>1、检出JeeSite4源代码：<br> </p>
<pre><code><br>git clone https://gitee.com/thinkgem/jeesite4.git
</code></pre>
<br>2、拷贝
<code>web</code>文件夹，到你的工作目录（不包含中文和空格的目录）下，重命名为你的工程名，如：
<code>jeesite-demo</code>
<p></p>
<p>3、打开<code>pom.xml</code>文件，修改第13行，artifactId为你的工程名，如：<code>&lt;artifactId&gt;jeesite-demo&lt;/artifactId&gt;</code></p>
<p>4、导入到Eclipse，菜单 File -&gt; Import，然后选择 Maven -&gt; Existing Maven Projects，点击 Next&gt; 按钮，选择第2步的<code>jeesite-demo</code>文件夹，然后点击 Finish 按钮，即可成功导入</p>
<p>5、这时，Eclipse会自动加载Maven依赖包，初次加载会比较慢（根据自身网络情况而定），若工程上有小叉号，请打开Problems窗口，查看具体错误内容，直到无错误为止</p>
<p>6、下载过程中你可以准备数据库环境了（若以上过程有问题，请看本页最下常见问题）</p>
<h1>初始化数据库</h1>
<p>1、以MySql为例，配置下SQL模式，否则建表的时候可能会出现问题</p> 
<pre><code>打开 my.ini 给 [mysqld] 增加如下配置：
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
</code></pre>
<p>2、 创建用户和授权</p> 
<pre><code>set global read_only=0;

create user 'jeesite'@'%' identified by 'jeesite';

create database jeesite DEFAULT CHARSET utf8 COLLATE utf8_general_ci;  

grant all privileges on jeesite.* to 'jeesite'@'%' identified by 'jeesite';

flush privileges;
</code></pre>
<p>3、打开文件 <code>/src/main/resources/config/jeesite.yml</code> 配置产品和项目名称及JDBC连接</p> 
<pre><code># 产品或项目名称、版本、版权年份
productName: JeeSite Demo
productVersion: V4.0
copyrightYear: 2018

# 软件提供商公司或个人名称
companyName: 你的公司或个人名称

# 数据库连接
jdbc: 

  # Mysql 数据库配置
  type: mysql
  driver: com.mysql.jdbc.Driver
  url: jdbc:mysql://127.0.0.1:3306/jeesite?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;zeroDateTimeBehavior=convertToNull
  username: jeesite
  password: jeesite
  testSql: SELECT 1
</code></pre>
<p>4、若刚才导入到的Eclipse的项目已经完成加载jar并无错误，你就可以执行<code>/bin/init-data.bat</code>脚本初始化数据库了，第一次执行如果遇见<code>table xxx doesn't exist</code>的错误信息，忽略即可。</p>
<p>5、如果执行bat不成功，你也可以直接在Eclipse里找到<code>com.jeesite.test.InitCoreData.java</code>和<code>com.jeesite.test.InitGenData.java</code>两个文件并打开，然后在空白处右键，点击 Run As -&gt; JUnit Test 运行单元测试进行初始化数据库脚本。</p>
<h1>启动Tomcat服务</h1>
<p>1、当前是Spring Boot工程，内部已经集成Web容器，你无需另外再下载Tomcat进行部署，只需按照以下方式进行即可。</p>
<p>2、打开<code>/src/main/resources/config/application.yml</code>文件，配置你的服务端口<code>port</code>、部署路径<code>context-path</code>，例如:</p> 
<pre><code>server:

  port: 8980
  context-path: /jeesite-demo
  tomcat:
    uri-encoding: UTF-8
</code></pre>
<p>3、在Eclipse里找到<code>com.jeesite.modules.config.Application.java</code>文件并打开，然后在空白处右键，点击 Debug As -&gt; Java Application 即可启动服务。</p>
<p>4、注意使用 Debug 运行，有助于你调试程序和提升开发效率，如：修改方法内的代码，修改视图代码，修改mapper代码，是不需要重启Web服务的。如果改变java类的结构，如果增删属性、方法、参数等，新增文件，这是就需要重启服务了。</p>
<h1>浏览器访问</h1>
<p>1、地址：<a href="http://127.0.0.1:8980/jeesite-demo" rel="nofollow">http://127.0.0.1:8980/jeesite-demo</a></p>
<p>2、默认最高管理员账号：system 密码：admin</p>
<p>5、这时已经配置完成，开启你的开发之旅吧</p>
<h1>清理Demo数据</h1>
<p>为了演示方法，初始化数据库后会载入一些演示数据，执行以下脚本即可清理</p> 
<pre><code>DELETE FROM js_sys_post;
DELETE FROM js_sys_office;
DELETE FROM js_sys_company;
DELETE FROM js_sys_employee;
DELETE FROM js_sys_employee_post;
DELETE FROM js_sys_role WHERE role_code NOT IN ('corpAdmin','default');
DELETE FROM js_sys_role_data_scope;
DELETE FROM js_sys_user WHERE user_code NOT IN ('system','admin');
DELETE FROM js_sys_user_data_scope;
DELETE FROM js_sys_user_role;
DELETE FROM js_sys_log;
</code></pre>
<h1>部署常见问题</h1> 
<ul> 
 <li><p>管理员账号system与admin的区别：system为开发者使用的最高级别管理员，主要用于开发和调试，有些修改会直接影响系统的正常运行；admin为客户方使用的系统管理员，用于一些基础数据配置，如机构、用户、权限、用户字典等。</p></li> 
 <li><p>下载不到<code>jeesite-xxx.jar</code>依赖包：相关包已经发布到<code>Sonatype Repository</code>快照仓库，在<code>web</code>项目的<code>pom.xml</code>里已配置，有可能是你的 Maven <code>setting.xml</code>里添加了<code>mirror</code>或<code>repository</code>导致的问题，请清理或追加添加<code>Sonatype Repository</code>仓库地址，再尝试执行 /web<code>/bin/package.bat</code> 脚本，预先下载依赖jar包即可。</p></li> 
 <li><p>IntelliJ IDEA中提示<code>NoClassDefFoundError: javax/servlet/ServletOutputStream</code>错误，你只需要修改web项目下的pom.xml,注释掉<code>spring-boot-starter-tomcat</code>的<code>&lt;scope&gt;provided&lt;/scope&gt;</code>部分改为<code>&lt;scope&gt;compile&lt;/scope&gt;</code>即可。</p></li> 
 <li><p>出现NoSuchMethodError错误，一般是依赖包版本需要更新，请尝试如下操作：在web项目上右键，选择菜单 -&gt; Maven -&gt; Update Project…（或按Alt+F5） -&gt; 点击 Select All 按钮 -&gt; 选择 Force Update of Snapshots/Releases 复选框 -&gt; 点击OK按钮即可。</p></li> 
 <li><p>MySql执行<code>init-data.bat</code>初始化数据失败，你可尝试设置下 my.ini 给 [mysqld] 增加如下配置：<code>sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION</code></p></li> 
</ul>
			</div>

																						<h3 id="text_267351" class="sin_target">数据持久层开发手册</h3>
<div class="sin_text">
    					<p>持久层实体类采用 @Table注解配置，自动生成增删改通用SQL，不需要在mapper.xml里写重复又费时的SQL，遇见复杂的情况下支持扩展。而报表统计分析的情况下又能支持mybatis原生写法，在写sql的时候，又能调用之前实体配置的一些参数。从而减少开发和后期维护成本。</p>
<p>众多的持久层框架@Column注解定义都是分布到get或属性上，或者干脆直接使用属性作为字段名，这在JeeSite是不推荐的，JeeSite的实体不仅仅是物理实体，它是与Model实体结合的一个产物。综合考虑，将@Column所有定义到类头，而不是分布到各个属性或方法上，主要是有以下三点原因：</p> 
<ol> 
 <li>可一览熟知该实体类对应的物理表结构是什么样，引领开发者思维从物理表结构到对象的映射转换，都是基于物理表结构的，@Column中的name指定物理字段名，而不是指定类上的属性名，也是这个原因；</li> 
 <li>自动化生成的SQL和查询条件，是有序的，可方便核查定义，优化查询；</li> 
 <li>方便@JoinTable关联表和其它扩展信息的设置，如果分布到类的属性上就需要来回滚动屏幕查找，不利于管理字段列。</li> 
</ol>
<h2>下面举例说明，最后附上API：</h2>
<p>以定义员工实体举例，配置如下：（注意代码上的注释）</p> 
<pre><code>@Table(name="${_prefix}sys_employee", alias="a", columns={

		// 支持Include，如：自动导入status、create_by、create_date等字段
		@Column(includeEntity=BaseEntity.class),
		@Column(includeEntity=DataEntity.class),        

		// 支持设置主键PK字段，调用get方法时自动加入主键唯一条件
		@Column(name="emp_code", label="员工编码", isPK=true),  

		// 支持设置查询字段类型，如LIKE自动在查询值前后加 % 符号。
		@Column(name="emp_name", label="名称", queryType=QueryType.LIKE),  
		@Column(name="emp_name_en", label="英文名", queryType=QueryType.LIKE),

		// 字段名到Java属性名的转换，采用驼峰命名法规则自动进行转换
		@Column(name="emp_no", label="工号"),   

		// 驼峰命名法转换不了的，支持设置特殊对象属性，如mapper.xml的sql中 a.office_code AS "office.officeCode" 的写法
		@Column(name="office_code", attrName="office.officeCode", label="机构编码"),  
		@Column(name="office_name", attrName="office.officeName", label="机构名称", queryType=QueryType.LIKE),
		@Column(name="company_code", attrName="company.companyCode", label="公司编码"),
		@Column(name="company_name", attrName="company.companyName", label="公司名称", queryType=QueryType.LIKE),

		@Column(name="sex", label="性别"),
		@Column(name="birthday", label="生日"),

		// 支持设置非查询字段，添加查询条件时忽略该字段
		@Column(name="photo", label="员工照片", isQuery=false), 

		@Column(name="email", label="电子邮件"),
		@Column(name="mobile", label="手机号码"),
		@Column(name="phone", label="办公电话"),
		@Column(name="fax", label="传真号码"),
		@Column(name="qq", label="QQ号"),
		@Column(name="weixin", label="微信号"),
		@Column(name="stations", label="岗位"),
	},
	// 支持联合查询，如左右连接查询，支持设置查询自定义关联表的返回字段列
	joinTable={
		@JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o", 
			on="o.office_code = a.office_name",
			columns={@Column(includeEntity=Office.class)}),
		@JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c", 
			on="c.company_code = a.company_name",
			columns={@Column(includeEntity=Company.class)}),
	},
	// 支持扩展Column、Form、Where等，主要用于该注解实现不了的复杂情况，扩展SQL写法，这里设置的是sqlMap的key
	extWhereKeys="dsfOffice, dsfCompany"
	// 自动设置默认排序
	orderBy="a.update_date DESC"
)
public class Employee extends DataEntity&lt;Employee&gt; {
	private static final long serialVersionUID = 1L;
	private String empCode;		// 员工编码
	private String empName;		// 名称
	private String empNameEn;	// 英文名
	private String empNo;		// 工号
	private Office office;	    // 机构编码
	private Company company;	// 公司编码
	private String sex;			// 性别
	private Date birthday;		// 生日
	private String photo;		// 员工照片
	private String email;		// 电子邮件
	private String mobile;		// 手机号码
	private String phone;		// 办公电话
	private String fax;			// 传真号码
	private String qq;			// QQ号
	private String weixin;		// 微信号
	private String stations;	// 岗位
	
	/// 省略  get  set 方法
	
}
</code></pre>
<p>请仔细看上面的代码和注释，其以上之外，还支持是否为插入字段，是否为更新字段等等。</p>
<p>再举一个例子，扩展上面介绍的Employee表，与用户表联合查询单独定义实体，用户员工实体：</p> 
<pre><code>@Table(name="${_prefix}sys_user", alias="a", columns={
		@Column(includeEntity=User.class),
	}, joinTable={
		@JoinTable(type=Type.JOIN, entity=Employee.class, alias="e",
			on="e.emp_code = a.ref_code AND a.user_type=#{USER_TYPE_EMPLOYEE}",
			columns={@Column(includeEntity=Employee.class)}),
		@JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o", 
			on="o.office_code = a.office_name", attrName="employee.office",
			columns={@Column(includeEntity=Office.class)}),
		@JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c", 
			on="c.company_code = a.company_name", attrName="employee.company",
			columns={@Column(includeEntity=Company.class)}),
	}, extWhereKeys="dsfOffice, dsfCompany", orderBy="a.update_date DESC"
)
public class EmpUser extends User {
	private static final long serialVersionUID = 1L;
	public EmpUser() {
		this(null);
	}
	public EmpUser(String id){
		super(id);
	}
	@Valid
	public Employee getEmployee(){
		Employee employee = (Employee)super.getRefObj();
		if (employee == null){
			employee = new Employee();
		}
		return employee;
	}
	public void setEmployee(Employee employee){
		super.setRefObj(employee);
	}
}
</code></pre>
<p>注解配置完成了，下面看看如何使用</p>
<h2>如何使用</h2>
<p>贴了这么多配置代码，下面介绍下用法。</p>
<p>你的Dao只需要继承CrudDao即可享受便捷体验，是不是特Easy，如下：</p> 
<pre><code>/**
 * 员工管理DAO接口
 * @author ThinkGem
 */
@MyBatisDao(entity = Employee.class)
public interface EmployeeDao extends CrudDao&lt;Employee&gt; {

}
</code></pre>
<p>EmployeeDao继承CrudDao后，里面的方法你都可以调用，如下方法：</p> 
<pre><code>/**
 * DAO实现增删改接口
 * @author ThinkGem
 */
public interface CrudDao&lt;T&gt; extends QueryDao&lt;T&gt; {

	/**
	 * 插入数据
	 */
	public int insert(T entity);
	
	/**
	 * 批量插入数据
	 */
	public int insertBatch(List&lt;T&gt; entityList);

	/**
	 * 更新数据  By PK
	 */
	public int update(T entity);
	
	/**
	 * 更新数据  By Entity
	 */
	public int updateByEntity(T entity, T whereEntity);
	
	/**
	 * 更新状态数据  By PK
	 */
	public int updateStatus(T entity);
	
	/**
	 * 更新状态数据  By Entity
	 */
	public int updateStatusByEntity(T entity, T whereEntity);
	
	/**
	 * 删除数据  By PK（如果有status字段，则为逻辑删除，更新status字段为1，否则物理删除）
	 */
	public int delete(T whereEntity);
	
	/**
	 * 删除数据  By Entity（如果有status字段，则为逻辑删除，更新status字段为1，否则物理删除）
	 */
	public int deleteByEntity(T whereEntity);

	/**
	 * 获取单条数据
	 * @param entity
	 * @return entity
	 */
	public T get(T entity);
	
	/**
	 * 获取单条数据
	 * @param entity
	 * @return entity
	 */
	public T getByEntity(T entity);
	
	/**
	 * 查询数据列表，如果需要分页，请设置分页对象，如：entity.setPage(new Page&lt;T&gt;(pageNo, pageSize));
	 * @param entity
	 * @return
	 */
	public List&lt;T&gt; findList(T entity);
	
	/**
	 * 查询数据总数
	 * @param entity
	 * @return
	 */
	public long findCount(T entity);
	
}
</code></pre>
<p>调用举例：</p> 
<pre><code>// 查询一条，更新
Employee employee = new Employee();
employee.setEmpCode('E001');
employee = employeeDao.get(employee);
employee.setMobile('18666666666');
employeeDao.update(employee);

// 列表查询、统计
Employee employee = new Employee();
employee.setEmpName('小王');
employee.setPage(new Page(1, 20)); // 分页查询
List&lt;Employee&gt; list = employeeDao.findList(employee);
Long count = employeeDao.findCount(employee);

// 批量插入
employeeDao.insertBatch(list);
</code></pre>
<p>是不是有种事半功倍的感觉，小小的配置，可以实现几乎可以完成原来需要写代码的80%时间。</p>
<p>也许你会觉着配置复杂，难以理解，只要你用上了相信你就会爱不释手。</p>
<p>还有一个惊喜，这些配置也可以通过代码生成工具快速生成，喜欢不喜欢。</p>
<p>嗯！基本增删改查，批量操作，按实体属性查询，按实体属性更新，以及统计都有了 ↓↓↓ 可是 ↓↓↓</p>
<h2>可是</h2>
<p>这么多还是还不够，比如，我们想实现，日期范围查询怎么办？某个实体属性，实现双重查询（如那么既能eq又能like）怎么办？想实现or、is null，括号查询怎么办？这些都么关系，已经替你考虑了，如下：</p> 
<pre><code><br>////////// 日期范围查询，gte，lte ////////////////

public Date getCreateDate_gte(){
	return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
}

public void setCreateDate_gte(Date createDate){
	createDate = DateUtils.getOfDayFirst(createDate); // 将日期的时间改为0点0分0秒
	sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
}

public Date getCreateDate_lte(){
	return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
}

public void setCreateDate_lte(Date createDate){
	createDate = DateUtils.getOfDayLast(createDate); // 将日期的时间改为23点59分59秒
	sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
}

////////// 双重字段查询，支持eq，支持like ////////////////

public String getTableName() {
	return StringUtils.lowerCase(tableName);
}

public void setTableName(String tableName) {
	this.tableName = tableName;
}

public String getTableName_like() {
	return sqlMap.getWhere().getValue("table_name", QueryType.LIKE);
}

public void setTableName_like(String tableName) {
	sqlMap.getWhere().and("table_name", QueryType.LIKE, tableName);
}

////////// 实现 or、is null，括号 ////////////////

public String getParentTableName_isNull() {
	return this.getParentTableName();
}

public void setParentTableName_isNull(String parentTableName) {
	if (StringUtils.isBlank(parentTableName)){
		sqlMap.getWhere().andBracket("parent_table_name", QueryType.IS_NULL, null, 2)
			.or("parent_table_name", QueryType.EQ_FORCE, "", 3).endBracket();
		this.setParentTableName(null);
	}else{
		this.setParentTableName(parentTableName);
	}
}
	
</code></pre>
<p>还有一种情况，如所有的配置都配置好了，我只需要在sql返回值里加一个简单的统计数，多返回一列，你可以这样写：</p> 
<pre><code>// 实体类定义
@Table(name="${_prefix}gen_table", alias="a", columns={
    	// @Column 。。。此处省略 。。。
    },
    // 扩展Column里指定一个Key名字，类里并定义一个需要返回的属性和get set
    extColumnKeys="extColumn"
)
public class GenTable extends DataEntity&lt;GenTable&gt; {
	private Long childNum;				// 子表个数
	public Long getChildNum() {
		return childNum;
	}
	public void setChildNum(Long childNum) {
		this.childNum = childNum;
	}
}

// Service 里，通过sqlMap设置你刚定义的Key即可，如下
public Page&lt;GenTable&gt; findPage(Page&lt;GenTable&gt; page, GenTable genTable) {
	// 添加扩展列，查询子表个数
	String extColumn = "(SELECT count(1) FROM "+MapperHelper.getTableName(genTable)
		+" WHERE parent_table_name=a.table_name) AS \"childNum\"";
	genTable.getSqlMap().add("extColumn", extColumn);
	return super.findPage(page, genTable);
}
</code></pre>
<p>如果以上仍得不到你的满足，怎么办，那你可以写Mapper.xml了，比如EmployeeDao.xml一些通用的字段、条件，你就不需要在xml再写一遍了，你只需要补充SQL即可（相同id,如id=“findList”则会自动覆盖默认设置）：</p> 
<pre><code>&lt;select id="findList" resultType="Employee"&gt;
	SELECT ${sqlMap.column.toSql('a')}
	FROM ${_prefix}sys_employee a
	&lt;where&gt;
		${sqlMap.where.toSql('a')}
	&lt;/where&gt;
	ORDER BY ${sqlMap.order.toSql('a')}
&lt;/select&gt;
</code></pre>
<p>这样的Dao，你满意吗？编码原来如此简单，提高了效率，又不损失灵活，是不是很有趣呢。</p>
<h2>附：API</h2>
<h3>@Table</h3> 
<pre><code>/**
 * 指定实体的物理表属性 
 * @author ThinkGem
 */
public @interface Table {

	/**
	 * 物理表名
	 */
	String name() default "";
	
	/**
	 * 当前表别名
	 */
	String alias() default "a";
	
	/**
	 * 表列定义
	 */
	Column[] columns();
	
	/**
	 * 查询，关联表
	 */
	JoinTable[] joinTable() default {};
	
	/**
	 * 指定排序
	 */
	String orderBy() default "";
	
	/**
	 * 表说明
	 */
	String comment() default "";
	
	/**
	 * 扩展ColumnSQL，在这里指定sqlMap的key。&lt;br&gt;
	 * 例如：\@Table(extColumnKeys="dataScopeColumn");&lt;br&gt;
	 * Service里设置：sqlMap.put("extColumn", "column_name AS \"columnName\"");&lt;br&gt;
	 * 在执行查询的时候，该语句放到自动会加到Where最后并执行。&lt;br&gt;
	 * &lt;b&gt;注意：&lt;/b&gt;如果设置，必须后台代码中设置，否则可能造成sql注入漏洞&lt;br&gt;
	 */
	String extColumnKeys() default "";
	
	/**
	 * 扩展FromSQL，在这里指定sqlMap的key。&lt;br&gt;
	 * 例如：\@Table(extFromKeys="dataScopeFrom");&lt;br&gt;
	 * Service里设置：sqlMap.put("dataScopeFrom", "JOIN table_name t on t.pk=a.pk");&lt;br&gt;
	 * 在执行查询的时候，该语句放到自动会加到Where最后并执行。&lt;br&gt;
	 * &lt;b&gt;注意：&lt;/b&gt;如果设置，必须后台代码中设置，否则可能造成sql注入漏洞&lt;br&gt;
	 */
	String extFromKeys() default "";
	
	/**
	 * 扩展WhereSQL，在这里指定sqlMap的key。&lt;br&gt;
	 * 例如：\@Table(extWhereKeys="dataScopeWhere");&lt;br&gt;
	 * Service里设置：sqlMap.put("dataScopeWhere", "AND column_name='value'");&lt;br&gt;
	 * 在执行查询的时候，该语句放到自动会加到Where最后并执行。&lt;br&gt;
	 * &lt;b&gt;注意：&lt;/b&gt;如果设置，必须后台代码中设置，否则可能造成sql注入漏洞&lt;br&gt;
	 */
	String extWhereKeys() default "";
	
}

</code></pre>
<h3>@Column</h3> 
<pre><code>/**
 * 定义物理表列属性（不继承父类注解）
 * @author ThinkGem
 */
public @interface Column {

	/**
	 * 字段名（例如：config_key）
	 */
	String name() default "";

	/**
	 * 属性名，若不指定，则根据name()字段名进行驼峰命名法转换（例如：config_key 转换为   configKey）
	 */
	String attrName() default "";

	/**
	 * 属性名，定义的类型
	 */
	Class&lt;?&gt; type() default Class.class;
	
	/**
	 * 标签名
	 */
	String label() default "";
	
	/**
	 * 字段备注
	 */
	String comment() default "";

	/**
	 * 是否主键（update、delete时的条件）
	 */
	boolean isPK() default false;
	
	/**
	 * 是否插入字段
	 */
	boolean isInsert() default true;
	
	/**
	 * 是否更新字段
	 */
	boolean isUpdate() default true;
	
	/**
	 * 是否是查询字段
	 */
	boolean isQuery() default true;
	
	/**
	 * 查询类型
	 */
	QueryType queryType() default QueryType.EQ;
	
	/**
	 * 包含嵌入一个实体
	 */
	Class&lt;?&gt; includeEntity() default Class.class;
}

</code></pre>
<h3>@JoinTable</h3> 
<pre><code>/**
 * 指定实体的物理表的关联表属性 
 * @author ThinkGem
 */
public @interface JoinTable {
	
	/**
	 * 连接类型
	 */
	Type type() default Type.JOIN;
	public enum Type{
		JOIN("JOIN"), // INNER JOIN
		LEFT_JOIN("LEFT JOIN"),
		RIGHT_JOIN("RIGHT JOIN");
		private final String value;
		Type(String value) { this.value = value; }
		public String value() { return this.value; }
	}
	
	/**
	 * 连接的表，指定实体Class
	 */
	Class&lt;?&gt; entity();
	
	/**
	 * 当前表别名
	 */
	String alias();
	
	/**
	 * 连接表条件
	 */
	String on();
	
	/**
	 * 对应主表中对应的属性名，若不指定，则根据entity()进行首字母小写得到属性名（例如：Config 转换为   config）
	 */
	String attrName() default "";
	
	/**
	 * 连接表，返回的列，若不指定，则读取entity()的所有列。
	 */
	Column[] columns() default {};
	
}
</code></pre>
<h3>QueryType</h3> 
<pre><code>/**
 * 查询类型
 * @author ThinkGem
 */
public enum QueryType{
	
	EQ("="),
	NE("!="),
	GT("&gt;"),
	GTE("&gt;="),
	LT("&lt;"),
	LTE("&lt;="),
	IN("IN"),
	NOT_IN("NOT IN"),
	LIKE("LIKE", "%", "%"),
	LEFT_LIKE("LIKE", "%", ""),
	RIGHT_LIKE("LIKE", "", "%"),
	IS_NULL("IS NULL"),
	IS_NOT_NULL("IS NOT NULL"),
	
	// 强制条件，不管值是不是空字符串都加载这个查询条件
	EQ_FORCE("=", true),
	NE_FORCE("!=", true),
	
	;
}
</code></pre>
			</div>

																						<h3 id="text_267352" class="sin_target">业务处理层开发手册</h3>
<div class="sin_text">
    					<p>对于业务逻辑层的开发重复代码很多，尽管有代码生成器，但从代码量总的来说还是比较多，所以就有了以下抽象类及工具，对一些常用操作进行封装。</p>
<p>对通用新增、删除、编辑、查询，代码操作进行封装简化。你只需要写你的业务逻辑代码就可以了。</p>
<p>对特有树状结构特有字段如（所有父级编码、所有排序号编码、是否是叶子节点、当前节点层次）进行更新，比如，通过所有父级编码可快速查询到所有子级的数据；通过所有排序号，可快速对整个树结构进行排序；通过是否叶子节点快速得知是否有下级；根据当前层次快速知道当前节点在树中的级别。</p>
<p>对通用数据权限进行简化封装，将颗粒度降到人员身上，支持人员与数据，角色与数据权限定制。数据权限不仅仅支持公司、部门、角色，还可以通过配置支持你的业务字段数据信息过滤，如订单的区域、内容管理的栏目、故障单类型等等。</p>
<p>对事务处理使用Spring事务@Transactional注解，进行方法级别的事务控制，不用单独处理事务及回滚。如配置传播行为，进行事务继承，子事务，事务回滚行为等，配置隔离级别读取未提交的数据等。</p>
<h2>基类及接口的继承关系</h2>
<p>TreeService -&gt; CrudService -&gt; QueryService -&gt; BaseService </p>
<p>TreeDao -&gt; CrudDao -&gt; QueryDao -&gt; BaseDao</p>
<p>TreeEntity -&gt; DataEntity -&gt; BaseEntity</p>
<h2>QueryService查询抽象基类</h2> 
<pre><code>/**
 * 新建实体对象
 * @return
 */
protected T newEntity();

/**
 * 新建实体对象（带一个String构造参数）
 * @return
 */
protected T newEntity(String id);

/**
 * 获取单条数据
 * @param id 主键
 * @return
 */
public T get(String id);

/**
 * 获取单条数据
 * @param entity
 * @return
 */
public T get(T entity);

/**
 * 获取单条数据，如果获取不到，则实例化一个空实体
 * @param id 主键编号
 * @param isNewRecord 如果是新记录，则验证主键编号是否存在。
 * 					     如果存在抛出ValidationException异常。
 * @return
 */
public T get(String id, boolean isNewRecord);

/**
 * 获取单条数据，如果获取不到，则实例化一个空实体（多个主键情况下调用）
 * @param pkClass 主键类型数组
 * @param pkValue 主键数据值数组
 * @param isNewRecord 如果是新记录，则验证主键编号是否存在。
 * 					     如果存在抛出ValidationException异常。
 * @return
 */
public T get(Class&lt;?&gt;[] pkClass, Object[] pkValue, boolean isNewRecord);

/**
 * 列表查询数据
 * @param entity
 * @return
 */
public List&lt;T&gt; findList(T entity);

/**
 * 分页查询数据
 * @param page 分页对象
 * @param entity
 * @return
 */
public Page&lt;T&gt; findPage(Page&lt;T&gt; page, T entity);

/**
 * 查询列表总数
 * @param entity
 * @return
 */
public long findCount(T entity);
</code></pre>
<h2>CrudService增删改抽象基类</h2>
<p>该类继承QueryService抽象类</p> 
<pre><code>/**
 * 保存数据（插入或更新）
 * @param entity
 */
@Transactional(readOnly = false)
public void save(T entity)

/**
 * 插入数据
 * @param entity
 */
@Transactional(readOnly = false)
public void insert(T entity);

/**
 * 更新数据
 * @param entity
 */
@Transactional(readOnly = false)
public void update(T entity);

/**
 * 更新状态（级联更新子节点）
 * @param entity
 */
@Transactional(readOnly = false)
public void updateStatus(T entity);

/**
 * 删除数据
 * @param entity
 */
@Transactional(readOnly = false)
public void delete(T entity);
</code></pre>
<h2>TreeService树结构抽象基类</h2>
<p>该类继承CrudService抽象类</p> 
<pre><code>/**
 * 根据父节点获取子节点最后一条记录
 */
public T getLastByParentCode(T entity);

/**
 * 保存数据（插入或更新）
 * 实现自动保存字段：所有父级编号、所有排序号、是否是叶子节点、节点的层次级别等数据
 * 实现级联更新所有子节点数据：同父级自动保存字段
 */
@Transactional(readOnly = false)
public void save(T entity);

/**
 * 更新parent_codes、tree_sorts、tree_level字段值
 */
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateParentCodes(T entity);

/**
 * 更新当前节点排序号
 */
@Transactional(readOnly = false)
public void updateTreeSort(T entity);

/**
 * 更新tree_leaf字段值
 */
@Transactional(readOnly = false, isolation = Isolation.READ_UNCOMMITTED) // 可读取未提交数据
private void updateTreeLeaf(T entity);

/**
 * 修正本表树结构的所有父级编号
 * 包含：数据修复（parentCodes、treeLeaf、treeLevel）字段
 */
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes();

/**
 * 按父级编码修正树结构的所有父级编号
 * 包含：数据修复（parentCodes、treeLeaf、treeLevel）字段
 */
@Transactional(readOnly = false) // 可读取未提交数据
public void updateFixParentCodes(String parentCode);

/**
 * 预留接口事件，更新子节点
 * @param childEntity 当前操作节点的子节点
 * @param parentEntity 当前操作节点
 */
protected void updateChildNode(T childEntity, T parentEntity);

/**
 * 更新状态（级联更新子节点）
 * @param entity
 */
@Transactional(readOnly = false)
public void updateStatus(T entity);

/**
 * 删除数据（级联删除子节点）
 * @param entity
 */
@Transactional(readOnly = false)
public void delete(T entity);

/**
 * 将不同级别无序的树列表进行排序，前提是sourcelist每一级是有序的&lt;br&gt;
 * 举例如下：&lt;br&gt;
 * 	List&lt;T&gt; targetList = Lists.newArrayList();&lt;br&gt;
 * 	List&lt;T&gt; sourcelist = service.findList(category);&lt;br&gt;
 * 	service.execTreeSort(targetList, sourcelist, "0");&lt;br&gt;
 * @param sourceList 源数据列表
 * @param targetList 目标数据列表
 * @param parentCode 目标数据列表的顶级节点
 */
public void execTreeSort(List&lt;T&gt; sourceList, List&lt;T&gt; targetList, String parentCode);

/**
 * 将简单列表code,parentCode转换为嵌套列表形式code,childList[code,childList[...]]&lt;br&gt;
 * 举例如下：&lt;br&gt;
 * 	List&lt;T&gt; targetList = Lists.newArrayList();&lt;br&gt;
 * 	List&lt;T&gt; sourcelist = service.findList(category);&lt;br&gt;
 * 	service.execChildListBulid(targetList, sourcelist, "0");&lt;br&gt;
 * @param sourceList 源数据列表
 * @param targetList 目标数据列表
 * @param parentCode 目标数据列表的顶级节点
 */
public void execChildListBulid(List&lt;T&gt; sourceList, List&lt;T&gt; targetList, String parentCode);
</code></pre>
<h2>数据权限调用</h2>
<p>本次对数据权限这块进行了全面的升级，让数据权限颗粒度细化到人员身上，支持人员与权限和角色与权限。</p>
<p>调用示例：</p> 
<pre><code>/**
 * 添加数据权限过滤条件
 */
public void addDataScopeFilter(T entity){
	// 举例1：公司数据权限过滤，实体类@Table注解extWhereKeys="dsf"
	company.getSqlMap().getDataScope().addFilter("dsf", "Company", "a.company_code");
	// 举例2：部门数据权限过滤，实体类@Table注解extWhereKeys="dsf"
	office.getSqlMap().getDataScope().addFilter("dsf", "Office", "a.office_code");
	// 举例3：角色数据权限过滤，实体类@Table注解extWhereKeys="dsf"
	role.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");
	// 举例4：用户、员工数据权限根据部门过滤，实体类@Table注解extWhereKeys="dsfOffice"
	empUser.getSqlMap().getDataScope().addFilter("dsfOffice",
			"Office", "e.office_code", "a.create_by");
	// 举例5：用户、员工数据权限根据公司过滤，实体类@Table注解extWhereKeys="dsfCompany"
	empUser.getSqlMap().getDataScope().addFilter("dsfCompany",
			"Company", "e.company_code", "a.create_by");
}
</code></pre>
<p>API接口:</p> 
<pre><code>/**
 * 数据范围过滤
 * @param sqlMapKey sqlMap的键值，举例：如设置“dsf”数据范围过滤，则：&lt;br&gt;
 * 		exists方式对应：sqlMap.dsf；   join方式对应：sqlMap.dsfForm 和 sqlMap.dsfWhere
 * @param ctrlTypes 控制类型，多个用“,”隔开，举例：&lt;br&gt;
 *		控制角色：Role&lt;br&gt;
 *		控制部门：Office&lt;br&gt;
 *		控制公司：Company&lt;br&gt;
 * @param bizCtrlDataFields 业务表对应过滤表别名或需要过滤的表别名加权限字段，多个使用“,”分隔，
 * 		长度必须与tableTypes保持一致，举例：&lt;br&gt;
 * 		业务表控制角色：a.role_code&lt;br&gt;
 * 		业务表控制部门：a.office_code&lt;br&gt;
 * 		业务表控制公司：a.company_code&lt;br&gt;
 * @param bizCtrlUserField 业务表对应表别名或用户字段。过滤只可以查看本人数据。
 * 		不设置的话，如果没有范围权限，则查不到任何数据，举例：&lt;br&gt;
 * 		业务表a.create_by"
 * @example
 * 		1）在Service中调用如下两种方式：&lt;br&gt;
 * 			// 添加数据权限过滤条件（控制角色）&lt;br&gt;
 * 			entity.getSqlMap().getDataScope().addFilter("dsf", "Role", "a.role_code");&lt;br&gt;
 * 			// 添加数据权限过滤条件（控制公司和部门）&lt;br&gt;
 * 			entity.getSqlMap().getDataScope().addFilter("dsf",&lt;br&gt;
 * 				"Office,Company", "a.office_code,a.company_code");&lt;br&gt;
 * 		2）MyBatis \@Table 注解中调用如下：&lt;br&gt;
 * 			采用 EXISTS方式调用	： \@Table(extWhereKeys="dsf")
 * 			采用JOIN方式调用	： \@Table(extFromKeys="dsf",extWhereKeys="dsf")
 * 		3）MyBatis Mapper 中调用如下两种方式：&lt;br&gt;
 * 			采用 EXISTS方式调用	： 将  ${sqlMap.dsf} 放在Where后&lt;br&gt;
 * 			采用JOIN方式调用	： 将  ${sqlMap.dsfFrom} 放在Form后 ，将  ${sqlMap.dsfWhere} 放在Where后&lt;br&gt;
 */
public QueryDataScope addFilter(String sqlMapKey, String ctrlTypes, String bizCtrlDataFields, String bizCtrlUserField);
</code></pre>
<h2>数据库事务</h2>
<p>事务管理对于企业应用来说是至关重要的，当出现异常情况，它也可以保证数据的一致性。</p>
<p>JeeSite主要使用Spring的@Transactional注解，也称声明式事务管理，是建立在AOP之上的。其本质是对方法前后进行拦截，然后在目标方法开始之前创建或者加入一个事务，在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务，这样就不需要在业务逻辑代码中掺杂事务管理的代码，只需通过基于@Transactional注解的方式，便可以将事务规则应用到业务逻辑中。</p>
<h3>注解属性</h3> 
<table>  
 <tbody><tr> 
  <th>属性 </th> 
  <th>类型 </th> 
  <th>描述</th> 
 </tr>  
 </tbody><tbody> 
  <tr> 
   <td>传播性 </td> 
   <td>枚举型 </td> 
   <td>可选的传播性设置（默认值：Propagation.REQUIRED）</td> 
  </tr> 
  <tr> 
   <td>隔离性 </td> 
   <td>枚举型 </td> 
   <td>可选的隔离性级别（默认值：Isolation.ISOLATION_DEFAULT）</td> 
  </tr> 
  <tr> 
   <td>只读性 </td> 
   <td>布尔型 </td> 
   <td>读写型事务 vs. 只读型事务</td> 
  </tr> 
  <tr> 
   <td>超时 </td> 
   <td>int型 </td> 
   <td>事务超时（以秒为单位）</td> 
  </tr> 
  <tr> 
   <td>回滚异常类（rollbackFor） </td> 
   <td>Class 类的实例，必须是Throwable 的子类 </td> 
   <td>异常类，遇到时必须进行回滚。默认情况下checked exceptions不进行回滚，仅unchecked exceptions（即RuntimeException的子类）才进行事务回滚。</td> 
  </tr> 
  <tr> 
   <td>不回滚异常类（noRollbackFor） </td> 
   <td>Class 类的实例，必须是Throwable的子类 </td> 
   <td>异常类，遇到时必须不回滚。</td> 
  </tr> 
 </tbody> 
</table>
<h3>事务传播行为</h3>
<p>所谓事务的传播行为是指，如果在开始当前事务之前，一个事务上下文已经存在，此时有若干选项可以指定一个事务性方法的执行行为。在Propagation定义中包括了如下几个表示传播行为的常量：</p> 
<ul> 
 <li>Propagation.REQUIRED：如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务，这是默认值。</li> 
 <li>Propagation.REQUIRES_NEW：创建一个新的事务，如果当前存在事务，则把当前事务挂起。</li> 
 <li>Propagation.SUPPORTS：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。</li> 
 <li>Propagation.NOT_SUPPORTED：以非事务方式运行，如果当前存在事务，则把当前事务挂起。</li> 
 <li>Propagation.NEVER：以非事务方式运行，如果当前存在事务，则抛出异常。</li> 
 <li>Propagation.MANDATORY：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。</li> 
 <li>Propagation.NESTED：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。</li> 
</ul>
<h3>事务隔离级别</h3>
<p>隔离级别是指若干个并发的事务之间的隔离程度。Isolation 接口中定义了五个表示隔离级别的常量：</p> 
<ul> 
 <li>Isolation.DEFAULT：这是默认值，表示使用底层数据库的默认隔离级别。对大部分数据库而言，通常这值就是Isolation.READ_COMMITTED。</li> 
 <li>Isolation.READ_UNCOMMITTED：该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读，不可重复读和幻读，因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。</li> 
 <li>Isolation.READ_COMMITTED：该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。</li> 
 <li>Isolation.REPEATABLE_READ：该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。</li> 
 <li>Isolation.SERIALIZABLE：所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。</li> 
</ul>
			</div>

																						<h3 id="text_267353" class="sin_target">前端页面视图开发手册</h3>
<div class="sin_text">
    					<p>前端CSS框架选用BootStrap和AdminLTE作为管理框架为基础，JS库仍然采用jQuery为核心框架，前端视图采用目前比较流行简单的Beetl模板引擎，取消了之前的JSP视图，为了减少耦合，并将所有视图文件分布到各个工程模块的资源目录下（如：/modules/core/src/main/resources/views/），JeeSite4.0对Beetl做了一个强化，主要包括如下几点：</p> 
<ul> 
 <li>常用函数库，如：字符串工具类，集合工具类，映射转换工具类，配置工具类，权限用户工具类等；</li> 
 <li>封装常用表单控件参考Spring MVC的form标签，实现更便捷的输入框、下拉框、单选、复选等，自动进行数据绑定；</li> 
 <li>封装常用表单组件，如：多级树结构选择组件，列表选择组件，文件上传组件，验证码生成组件等等；</li> 
 <li>封装常用JS类库，如：动态加载，对话框，消息框，加载框，JS模板，Ajax，格式化，动态Tab等待；</li> 
 <li>封装JS数据表格组件DataGrid，分页，排序，多表头，分组，子表，冻结，小计，合计，编辑行，树表表格等；</li> 
 <li>提供丰富例子，如：Box盒子、表单布局、栅格布局、图表等等</li> 
</ul>
<h2>模板语言界定符选择</h2>
<p>Beetl模板语言类似JS语言和习俗，只需要将Beetl语言放入定界符号里即可，JeeSite使用jsp标准定界符<code>&lt;%</code> <code>%&gt;</code>来作为模板语言的定界符，一方同比较容易被理解，并明确是后端运行的语法，另一方面冲突少，比较好界定边界</p>
<h3>举例一个通用布局的页面</h3> 
<pre><code>&lt;% layout('/layouts/default.html', {title: '菜单管理', libs: ['validate'], bodyClass: ''}){ %&gt;
&lt;div class="main-content"&gt;
	&lt;div class="box box-main"&gt;
		&lt;div class="box-header with-border"&gt;
			&lt;div class="box-title"&gt;
				&lt;i class="fa icon-book-open"&gt;&lt;/i&gt; 菜单管理
			&lt;/div&gt;
			&lt;div class="box-tools pull-right"&gt;
				&lt;button type="button" class="btn btn-box-tool" data-widget="collapse"&gt;&lt;i class="fa fa-minus"&gt;&lt;/i&gt;&lt;/button&gt;
			&lt;/div&gt;
		&lt;/div&gt;
		&lt;div class="box-body"&gt;
			
		&lt;/div&gt;
		&lt;div class="box-footer"&gt;
			
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;
&lt;% } %&gt;
</code></pre>
<p><strong>调用默认布局</strong> /layouts/default.html，自动引入页面头部和尾部内容，通过参数设置要加载的css和js类库，参数如下：</p>
<p><strong>title参数：</strong> 设置页面的标题名字</p>
<p><strong>libs参数：</strong> 设置页面要引入的css和js类库，支持类库如下：</p>
<p>默认引入：layer、select2、WdatePicker</p> 
<ul> 
 <li>zTree：树结构控件</li> 
 <li>tabPage：动态页签插件</li> 
 <li>dataGrid：数据表格组件</li> 
 <li>validate：表单验证组件</li> 
 <li>inputmask：表单格式化工具</li> 
 <li>fileupload：文件上传插件</li> 
 <li>ueditor：富文本编辑器控件</li> 
</ul>
<p><strong>bodyClass参数：</strong> 设置body的class属性值</p>
<h2>定义常用函数库</h2>
<h3>常用工具类导入</h3>
<p>以下工具类可通过@类型快速调用，如：${@Global.getConfig(‘key’)}</p> 
<ul> 
 <li>Global：全局配置类，全局常量，读取属性文件参数值等</li> 
 <li>EncodeUtils：封装各种格式的编码解码工具类，HEX、Base64、HTML、URL、XSS过滤、SQL过滤等</li> 
 <li>ListUtils：List常用工具类，继承Apache的ListUtils，New工具、快速提取属性值、类型转换等</li> 
 <li>MapUtils：Map常用工具类，继承Apache的MapUtils，New工具、Map与Bean互转等</li> 
 <li>SetUtils：Set常用工具类，继承Apache的SetUtils，New工具等</li> 
 <li>IdGenerate：封装各种生成唯一性ID算法的工具类，生成LongUUID，StringUUID，Code生成等</li> 
 <li>ByteUtils：字节转换工具</li> 
 <li>DateUtils：日期工具类，继承Apache的DateUtils</li> 
 <li>NumberUtils：BigDecimal工具类，继承Apache的NumberUtils类</li> 
 <li>ObjectUtils：对象操作工具类，继承Apache的ObjectUtils类</li> 
 <li>StringUtils：字符串工具类，继承Apache的StringUtils类</li> 
 <li>TimeUtils：时间计算工具类，xx天xx时xx分xx秒，刚刚，xx秒，xx分钟，xx小时前、xx天前</li> 
 <li>WorkDayUtils：工作日计算工具类，计算日期直接的工作日等</li> 
 <li>BeanMapper：简单封装Dozer，对象数据映射</li> 
 <li>JaxbMapper：Jaxb实现XML与Java Object的转换</li> 
 <li>JsonMapper：简单封装Jackson，实现Json与Java Object的转换</li> 
 <li>ClassUtils：Class扫描工具类，根据接口查询类，根据继承查询类等</li> 
 <li>ReflectUtils：反射工具类，方便进行getter/setter方法, 访问私有变量, 调用私有方法</li> 
 <li>ModuleUtils：模块工具类，方便获取系统模块信息</li> 
 <li>UserUtils：用户工具类，方便获取进行用户及相关信息</li> 
</ul>
<p><strong>以下是Beetl函数及扩展函数</strong></p> 
<ul> 
 <li>date：返回一个java.util.Date类型的变量，如 date() 返回一个当前时间(对应java的java.util.Date); ${date( “2011-1-1” , “yyyy-MM-dd” )} 返回指定日期</li> 
 <li>print：打印一个对象 print(user.name);</li> 
 <li>println：打印一个对象以及回车换行符号，回车换号符号使用的是模板本身的，而不是本地系统的.如果仅仅打印一个换行符，则直接调用println() 即可</li> 
 <li>nvl：函数nvl，如果对象为null，则返回第二个参数，否则，返回自己 nvl(user,“不存在”)</li> 
 <li>isEmpty：判断变量或者表达式是否为空，变量不存在，变量为null，变量是空字符串，变量是空集合，变量是空数组，此函数都将返回true</li> 
 <li>isNotEmpty：同上，判断对象是否不为空</li> 
 <li>has：变量名为参数，判断是否存在此全局变量，如 has(userList),类似于1.x版本的exist(“userList”),但不需要输入引号了</li> 
 <li>assert：如果表达式为false，则抛出异常</li> 
 <li>trim：截取数字或者日期，返回字符,如trim(12.456,2)返回“12.45”,trim(date,‘yyyyy’)返回“2017”</li> 
 <li>trunc：截取数字，保留指定的小数位，如trunc(12.456,2) 输出是12.45.不推荐使用，因为处理float有问题，兼容原因保留了</li> 
 <li>decode：一个简化的if else 结构，如 decode(a,1,“a=1”,2,“a=2”,“不知道了”)},如果a是1，这decode输出“a=1”,如果a是2，则输出“a==2”, 如果是其他值，则输出“不知道了”</li> 
 <li>debug：在控制台输出debug指定的对象以及所在模板文件以及模板中的行数，如debug(1),则输出1 [在3行@/org/beetl/core/lab/hello.txt],也可以输出多个，如debug(“hi”,a),则输出hi,a=123,[在3行@/org/beetl/core/lab/hello.txt]</li> 
 <li>range：接收三个参数，初始值，结束值，还有步增（可以不需要，则默认为1），返回一个Iterator，常用于循环中，如for(var i in range(1,5)) {print(i)},将依次打印1234.</li> 
 <li>flush：强制io输出。</li> 
 <li>pageCtx：仅仅在web开发中，设置一个变量，然后可以在页面渲染过程中，调用此api获取，如pageCtx(“title”,“用户添加页面”)，在其后任何地方，可以pageCtx(“title”) 获取该变量</li> 
 <li>cookie：返回指定的cookie对象 ，如var userCook = cookie(“user”), allCookies = cookie();</li> 
 <li>isBlank：判断对象是否是一个空字符串，${isBlank(‘str’)}</li> 
 <li>isNotBlank：判断对象是否不是一个空字符串，${isBlank(‘str’)}</li> 
 <li>toJson：将对象转Json字符串，${toJson(Object)}</li> 
 <li>fromJson：将Json字符串转换为对象，${fromJson(Object)}</li> 
 <li>hasPermi：判断是否有改权限；单个权限验证：${hasPermi(‘sys:user:edit’)}；多个AND关系：${hasPermi(‘sys:user:view,sys:user:edit’, ‘and’)}; 多个OR关系：${hasPermi(‘sys:user:view,sys:user:edit’, ‘or’)}</li> 
 <li>cookie：获取cookie值，${cookie(name, isRemove)}</li> 
 <li>lang: 获取当前国际化语言名称，例如：lang()，返回：zh_CN、en、等</li> 
 <li>text: 获取当前国际化代码译文，例如：text(‘中国’)，当前语言英文状态下，返回：China；支持格式化参数，如:text(‘第{0}天’, 3)</li> 
</ul>
<p><strong>数据类型格式化</strong></p>
<p>日期格式化：</p> 
<pre><code>Today is ${date,dateFormat="yyyy-MM-dd"}
Today is ${date,dateFormat}
如果date为日期类型可简写：
${date,“yyyy-MM-dd”}
</code></pre>
<p>数值格式化：</p> 
<pre><code>Salary is ${salary,numberFormat="##.##"}
</code></pre>
<h2>基本控件封装（类似Spring MVC表单标签)</h2>
<h3>form 表单标签</h3> 
<pre><code>生成一个form标签，支持指定model属性，类似SpringMVC的&lt;form:form modelAttribute=""/&gt;标签的属性，自动给表单内的控件绑定属性值
&lt;#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" class="form-horizontal"&gt;
    表单内容
&lt;/#form:form&gt;
支持上传文件：
&lt;#form:form id="inputForm" model="${user}" action="${ctx}/sys/user" method="POST" enctype="multipart/form-data" class="form-horizontal"&gt;
    表单内容
&lt;/#form:form&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 表单ID
	model: model!,				// 绑定Model对象，例如：${user!}
	action: action!,			// 表单请求地址
	method: method!,			// 请求方法，默认 post
	enctype: enctype!,			// 发送之前进行数据编码，上传文件时指定：multipart/form-data
	
};
</code></pre>
<p></p>
<h3>input 输入框标签</h3> 
<pre><code>自动绑定form:form上指定的model下的userName属性，类似SpringMVC的&lt;form:input path=""/&gt;标签的属性
&lt;#form:input path="userName" maxlength="100" class="form-control required "/&gt;
日期格式化：
&lt;#form:input path="userName" maxlength="100" dataFormat="date" class="form-control required "/&gt;
数值格式化：
&lt;#form:input path="userName" maxlength="100" dataFormat="number" class="form-control required "/&gt;
不自动绑定，把path改为name就可以：
&lt;#form:input name="userName" value="${user.userName}" maxlength="100" class="form-control required "/&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	type: type!'text',			// 元素的类型，默认text
	
	dataFormat: dataFormat!'',	// 数据格式化，支持如下值：
								// date: 日期；
								// datetime: 日期时间；
								// number: 数值类型，保留2位小数
	
};
</code></pre>
<p></p>
<h3>select 下拉框标签</h3> 
<pre><code>根据字典类型设置下拉数据：
&lt;#form:select path="userType" dictType="sys_user_type" class="form-control required " /&gt;
增加一个空白选项：
&lt;#form:select path="roleType" dictType="sys_role_type" blankOption="true" class="form-control " /&gt;
多选下拉列表：
&lt;#form:select path="roleType" dictType="sys_role_type" multiple="true" class="form-control " /&gt;
手动设置下拉框值，类似SrpingMVC的&lt;form:options items="" itemLabel="" itemValue=""/&gt;标签的属性：
&lt;#form:select path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" /&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	dictType: dictType!,		// 字典类型，从字典里获取，自动设置items、itemLabel、itemValue
	
	items: items![],			// 列表数据，可接受对象集合，如：List&lt;DictData&gt;
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
	multiple: multiple!'false', // 是否为多选框
	
	blankOption: @ObjectUtils.toBoolean(blankOption!false), // 是否默认有个空白选择项目
	
};
</code></pre>
<p></p>
<h3>radio 输入框标签</h3> 
<pre><code>类似&lt;#form:select/&gt;标签的使用方法
&lt;#form:radio path="menuType" dictType="sys_menu_type" class="form-control required" /&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	dictType: dictType!,		// 字典类型，从字典里获取，自动设置items、itemLabel、itemValue
	
	items: items!([]),			// 列表数据，可接受对象集合，如：List&lt;DictData&gt;
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
};
</code></pre>
<p></p>
<h3>checkbox 复选框标签</h3> 
<pre><code>类似&lt;#form:select/&gt;标签的使用方法，后台接受moduleCodes为字符串，选择多个自动使用“,”分隔，相比SpringMVC必须是List方便的多
&lt;#form:checkbox path="moduleCodes" items="${moduleList}" itemLabel="moduleName" itemValue="moduleCode" class="form-control required" /&gt;
生成一个复选框按钮，后台接受replaceFile为Global.YES或Global.NO值：
&lt;#form:checkbox path="replaceFile" label="是否替换现有文件" class="form-control"/&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	dictType: dictType!'',		// 字典类型，从字典里获取，自动设置items、itemLabel、itemValue
	
	items: items!([]),			// 列表数据，可接受对象集合，如：List&lt;DictData&gt;
	itemLabel: itemLabel!'',	// 指定列表数据中的什么属性名作为option的标签名
	itemValue: itemValue!'',	// 指定列表数据中的什么属性名作为option的value值
	
	label: label!,				// 只有一个复选按钮的情况下设置
	
};
</code></pre>
<p></p>
<h3>textarea 文本域标签</h3> 
<pre><code>&lt;#form:textarea path="remarks" rows="3" maxlength="200" class="form-control"/&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
};
</code></pre>
<p></p>
<h3>hidden 隐藏域标签</h3> 
<pre><code>&lt;#form:hidden path="menuCode" /&gt;
</code></pre>
<p>控件属性：<br> </p>
<pre><code><br>var p = {
	
	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	type: type!'hidden',		// 元素的类型，默认hidden
	
};
</code></pre>
<p></p>
<h2>表单组件封装</h2>
<h3>treeselect 树结构选择</h3>
<p>封装layer+zTree实现树结构选择组件，使用场景如：部门选择，行政区划选择，栏目列表选择等</p> 
<pre><code>&lt;#form:treeselect id="parent" title="上级菜单"
    	name="parent.id" value="${menu.parent.id!}"
    	labelName="parent.menuName" labelValue="${menu.parent.menuName!}"
    	url="${ctx}/sys/menu/treeData?excludeCode=${menu.menuCode}&amp;sysCode=${menu.sysCode}&amp;isShowCode=2"
    	class="" allowClear="true" canSelectRoot="true" canSelectParent="true"/&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {
	
		// 标签参数
		id: id!,					// 元素ID
		
		name: name!,				// 隐藏域名称
		value: value!,				// 隐藏域值
		
		labelName: labelName!,		// 标签框名称
		labelValue: labelValue!,	// 标签框值
		
		class: class!'',			// 标签框的CSS类名
		placeholder: placeholder!,	// 标签框的预期值的提示信息
		dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
		
		btnClass: btnClass!,		// 标签框后面的按钮CSS类名
		
		title: title!'选项',			// 对话框标题
		boxWidth: boxWidth!300, 		// 对话框宽度，默认300像素
		boxHeight: boxHeight!400,		// 对话框高度，默认400像素
		
		url: url!,					// 树结构，数据源地址 [{id, pid, name}]
		
		readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式
		
		allowInput: @ObjectUtils.toBoolean(allowInput!false),	// 是否允许label框输入
		allowClear: @ObjectUtils.toBoolean(allowClear!true),	// 是否允许清空选择内容
		
		checkbox: @ObjectUtils.toBoolean(checkbox!false),	// 是否显示复选框，是否支持多选，如果设置canSelectParent=true则返回父节点数据
		expandLevel: @ObjectUtils.toInteger(expandLevel!(-1)),		// 默认展开层次级别（默认:如果有1个根节点，则展开一级节点，否则不展开）
		
		canSelectRoot: @ObjectUtils.toBoolean(canSelectRoot!false),		// 可以选择跟节点
		canSelectParent: @ObjectUtils.toBoolean(canSelectParent!false),	// 可以选择父级节点
		
		returnFullName: @ObjectUtils.toBoolean(returnFullName!false),	// 是否返回全路径，包含所有上级信息，以 returnFullNameSplit 参数分隔
		returnFullNameSplit: returnFullNameSplit!'/',					// 是否返回全路径，的分隔符，默认“/”
		
	};
</code></pre>
<p></p>
<h3>iconselect 图标选择</h3> 
<pre><code>&lt;#form:iconselect path="menuIcon" class=""/&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {
	
	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	class: class!'',			// 隐藏域和标签框的CSS类名
	
};
</code></pre>
<p></p>
<h3>validcode 验证码</h3> 
<pre><code>&lt;#form:validcode name="validCode" isRequired="true" isRemote="true" /&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {

	id: id!name,				// 验证码输入框ID
	name: name!,				// 验证码输入框名称（必填）
	
	isRequired: @ObjectUtils.toBoolean(isRequired!true),	// 是否必填，默认必填
	dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
	
	isRemote: @ObjectUtils.toBoolean(isRemote!true),		// 是否支持实时远程验证
	dataMsgRemote: thisTag.attrs['data-msg-remote'],		// 必填错误提示信息
	
	isLazy: @ObjectUtils.toBoolean(isLazy!false),			// 是否懒加载验证码图片，原noRefresh参数

};
</code></pre>
<p></p>
<h3>listselect 列表选择</h3> 
<pre><code>&lt;#form:listselect id="userSelect" title="用户"
	url="${ctx}/sys/user/userSelect?userType=${role.userType}" allowClear="false" 
	checkbox="true" itemCode="userCode" itemName="userName"/&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID
	
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 隐藏域名称
	value: value!,				// 隐藏域值
	
	labelPath: labelPath!,		// 绑定form上model中属性的值
	labelName: labelName!,		// 标签框名称
	labelValue: labelValue!,	// 标签框值
	
	class: class!'',			// 标签框的CSS类名
	placeholder: placeholder!,	// 标签框的预期值的提示信息
	dataMsgRequired: thisTag.attrs['data-msg-required'],	// 必填错误提示信息
	
	btnClass: btnClass!,		// 标签框后面的按钮CSS类名
	
	title: title!'选项',			// 对话框标题
	boxWidth: boxWidth!'$(top.window).width() - 100', 		// 对话框宽度
	boxHeight: boxHeight!'$(top.window).height() - 100',	// 对话框高度 
	
	url: url!,					// 树结构，数据源地址 [{id, pid, name}]
	
	readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式
	
	allowInput: @ObjectUtils.toBoolean(allowInput!false),	// 是否允许label框输入
	allowClear: @ObjectUtils.toBoolean(allowClear!true),	// 是否允许清空选择内容
	
	checkbox: @ObjectUtils.toBoolean(checkbox!false),	// 是否显示复选框，是否支持多选，如果设置canSelectParent=true则返回父节点数据
	
	itemCode: itemCode!,	// 选择后结果集中的Code属性名，返回到隐藏域的值
	itemName: itemName!,	// 选择后结果集中的Name属性名，返回到输入框的值
	
};
</code></pre>
<p></p>
<h3>fileupload 文件上传</h3> 
<pre><code>1、文件上传：
&lt;#form:fileupload id="upload1" bizKey="${user.id}" bizType="user_upload1"
	uploadType="all" class="required" readonly="false"/&gt;
后台代码：FileUploadUtils.saveFileUpload(user.getId(), "user_upload1");

2、图片上传：
&lt;#form:fileupload id="upload2" bizKey="${user.id}" bizType="user_upload2"
	uploadType="image" class="required" readonly="false"/&gt;
后台代码：FileUploadUtils.saveFileUpload(user.getId(), "user_upload2");

3、返回路径：
&lt;#form:fileupload id="upload3" returnPath="true"
	filePathInputId="upload3Path" fileNameInputId="upload3Name"
	uploadType="image" readonly="false" maxUploadNum="3" isMini="false"/&gt;
&lt;#form:input name="upload3Path" class="form-control"/&gt;
&lt;#form:input name="upload3Name" class="form-control"/&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID
	
	bizKey: bizKey!,			// 业务表的主键值（与附件关联的业务数据）
	bizType: bizType!,			// 业务表的上传类型（全网唯一，推荐格式：实体名_上传类型，例如，文章图片：article_photo）
	
	returnPath: @ObjectUtils.toBoolean(returnPath!false), 	// 是否是返回文件路径到输入框（默认false），可将路径直接保存到某个字段里
	filePathInputId: filePathInputId!,	// 设置文件URL存放的输入框的ID，当returnPath为true的时候，返回文件URL到这个输入框
	fileNameInputId: fileNameInputId!,	// 设置文件名称存放的输入框的ID，当returnPath为true的时候，返回文件名称到这个输入框
	
	uploadType: uploadType!'',			// 上传文件类型：all、file、image、media，若不设置，则自动根据上传文件后缀获取
	class: class!'',					// 标签框的CSS类名，设置 required 加入必填验证
	readonly: @ObjectUtils.toBoolean(readonly!false),		// 是否只读模式，只读模式下为查看模式，只允许下载
	
	allowSuffixes: allowSuffixes!'', 	// 允许上传的后缀，前台的限制，不能超越file.*AllowSuffixes的设置，例如：.jpg,.png,
	maxUploadNum: @ObjectUtils.toInteger(maxUploadNum!300),		// 多文件下允许最多上传几个，默认300个，设置-1代表不限制
	
	imageMaxWidth: @ObjectUtils.toInteger(imageMaxWidth!1024),	// 图片压缩，最大宽度（uploadType为image生效），设置-1代表不做任何处理
	imageMaxHeight: @ObjectUtils.toInteger(imageMaxHeight!768),	// 图片压缩，最大宽度（uploadType为image生效），设置-1代表不做任何处理
	
	isLazy: @ObjectUtils.toBoolean(isLazy!false),				// 设置为ture需要点击上传按钮才上传文件，否则选择后就直接上传
	
	isMini: @ObjectUtils.toBoolean(isMini!false),				// 是否是精简上传窗口，无边距，无边框
	
	preview: preview!'',										// 是否显示预览按钮，接受参数：weboffice
	
};
</code></pre>
<p></p>
<h3>imageclip 图片裁剪</h3> 
<pre><code>&lt;img id="avatarImg" class="profile-user-img img-responsive img-circle"
    src="${@user.getAvatarUrl().replaceFirst('/ctxPath', ctxPath)}"&gt;
&lt;#form:imageclip name="imageBase64" btnText="修改头像" btnClass="btn-block"
	imageId="avatarImg" imageDefaultSrc="${ctxStatic+'/images/user1.jpg'}"
	circle="true"/&gt;
后台代码：
// 如果设置了头像，则保存头像
if (StringUtils.isNotBlank(imageBase64)){
	if ("EMPTY".equals(imageBase64)){
		user.setAvatar(StringUtils.EMPTY);
	}else{
		String imageUrl = "avatar/"+user.getUserCode()
				+"."+FileUtils.getFileExtensionByImageBase64(imageBase64);
		String fileName = Global.getUserfilesBaseDir(imageUrl);
		FileUtils.writeToFileByImageBase64(fileName, imageBase64);
		user.setAvatar(Global.USERFILES_BASE_URL + imageUrl);
	}
}
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,					// 元素ID，如果不填写，则与name相同
	path: path!,				// 绑定form上model中属性的值
	name: name!,				// 元素名称，不填写
	value: value!,				// 元素值
	
	class: class!'',			// 隐藏域的CSS类名
	
	btnText: btnText!'选择图片',	// 按钮的名字
	btnClass: btnClass!'',		// 按钮的CSS类名
	
	imageId: imageId!'',					// 裁剪后base64返回到img的id
	imageDefaultSrc: imageDefaultSrc!'',	// 图片默认地址，清除后使用地址
	
	circle: circle!'false', 	// 是否圆形图片
	
};
</code></pre>
<p></p>
<h3>ueditor 富文本在线编辑器</h3> 
<pre><code>&lt;#form:ueditor name="text" maxlength="10000" height="200" class="required"
	simpleToolbars="false" readonly="false" outline="false"/&gt;
</code></pre>
<p>组件属性：<br> </p>
<pre><code><br>var p = {

	// 标签参数
	id: id!,				// 元素ID，如果不填写，则与name相同
	path: path!,			// 绑定form上model中属性的值
	name: name!,			// 元素名称，不填写
	value: value!,			// 元素值
	
	class: class!'',		// 标签框的CSS类名，设置 required 加入必填验证
	
	maxlength: maxlength!'',	// 编辑器最大输入字数，为空代表无限制
	height: height!'200',		// 编辑器的高度，默认200
	
	simpleToolbars: @ObjectUtils.toBoolean(simpleToolbars!false), // 是否是简单的工具条
	
	readonly: @ObjectUtils.toBoolean(readonly!false), 	// 是否只读模式
	
	outline: @ObjectUtils.toBoolean(outline!false), 	// 大纲视图
	
	options: options!'',	// UE附加选项，逗号隔开。
	
};
</code></pre>
<p></p>
<h2>封装通用JavaScript方法</h2> 
<pre><code><br>/**
 * 输出日志
 */
log(msg);

/**
 * 输出错误日志
 */
error(msg);

/**
 * URL 编码
 */
js.encodeUrl(url);

/**
 * URL 解码
 */
js.decodeUrl(url);

/**
 * 得到 IE 版本，如果是IE返回：IE版本号，否则返回：false
 * if (js.ie &amp;&amp; js.ie &lt;= 8){ alert('浏览器版本过低') }
 */
js.ie;

/**
 * 安全取值，复杂类型或嵌套类型时，取不到属性中的属性时不抛出异常
 * js.val(jsonObj, 'user.office.name');
 */
js.val(jsonObj, attrName);

/**
 * 返回HashCode唯一值（默认忽略大小写）
 * @param str 要获取的字符串HashCode值
 * @param caseSensitive 是否大小写敏感（默认false）
 * @usage js.hashCode(str);
 */
js.hashCode(str, caseSensitive);

/**
 * 异步加载文件，loadFile v1.0
 * js.loadFile(file文件路径, callback成功回调, error失败回调)
 * js.loadFile('js/test.js',function(){},function(data){});
 * js.loadFile(['js/test.js','css/test.css'],function(){},function(data){});
 */
js.loadFile(file, callback, error);

/**
 * 打开一个Window窗体
 */
js.windowOpen(url, name, width, height);

/**
 * 关闭当前Window窗体
 */
js.windowClose();

/**
 * 给URL地址添加参数，如果原来有参数则用&amp;前缀，如果没有则用?前缀
 */
js.addParam(url, params);

/**
 * 获取URL地址的参数
 */
js.getParam(paramName, url);

/**
 * 查看Object的内容，手机调试用
 * @param obj
 */
js.alertObj(obj);

/**
 * 获取国际化译文
 */
js.text(code, params);

/**
 * 获取字典标签
 * js.getDictLabel(${@DictUtils.getDictListJson('sys_menu_type')}, val, '未知', true)
 */
js.getDictLabel(dictListJson, value, defaultValue, inCss);

/////////////////////////////////////// message dialog

/**
 * 显示加载框
 * @param message 加载框提示信息
 * @param ignoreMessageIfExists 如果已经有加载框现在，则忽略message信息的设置
 * @usage js.loading('正在保存...');
 */
js.loading(message, ignoreMessageIfExists);

/**
 * 关闭加载框
 * @param timeout 关闭延迟时间
 * @param forceClose 是否强制关闭
 * @usage js.closeLoading(1000, true);
 */
js.closeLoading(timeout, forceClose);

/**
 * 得到layer对话框对象
 * js.layer.msg();
 */
js.layer;

/**
 * 显示提示框
 * @param message 提示消息
 * @param title 提示标题
 * @param type 提示类型（success、error、warning、info）
 * @param timeout 自动关闭毫秒（默认4000毫秒）
 */
js.showMessage(message, title, type, timeout);

/**
 * 显示错误提示框
 */
js.showErrorMessage(responseText);

/**
 * 关闭提示框
 */
js.closeMessage();

/**
 * 提示对话框
 * @param message 提示消息
 * @param options 对话框选项
 * @param closed 对话框关闭回调方法
 * @usage js.alert('你好！', function(){})
 * @usage js.alert('你好！', {icon: 2}, function(){})
 */
js.alert(message, options, closed);

/**
 * 确认对话框
 * @param message 确认信息
 * @param urlOrFun 确认后的跳转的地址，或调用的方法
 * @param data 如果urlOrFun是地址，该参数是调用地址的参数信息
 * @param callback 执行ajax的回调方法，如果为空，则直接通过location=urlOrFun跳转。
 * @param dataType 返回数据类型（默认json）
 * @param async 是否异步（默认true）
 * @param loadingMessage 调用loading(loadingMessage)的提示信息。
 * @usage js.confirm('确认删除吗？', '$ctx/biz/delete?id=123', function(data){alert('删除成功')}, 'json', true, '正在删除...');
 * @usage js.confirm('确认删除吗？', '$ctx/biz/delete', {id: '123'}, function(data){alert('删除成功')}, 'json', true, '正在删除...');
 * @usage js.confirm('确认删除吗？', function(data){alert('删除成功')});
 */
js.confirm(message, urlOrFun, data, callback, dataType, async, loadingMessage);

/////////////////////////////////////// js template

/**
 * 根据js模板生成代码，使用laytpl引擎
 * @param id 模板ID
 * @param data 模板数据（可选）
 * @param callback 如果填写，则为异步渲染
 * @usage 
 * 		模板格式： &lt;sc ript id="dempTpl" type="text/template"&gt;//&lt;!--
 * 					 这里写模块内容...
 * 				//--&gt;&lt;/sc ript&gt;
 * 		调用方法： js.template('dempTpl', data);
 * 		模版语法：
 * 			输出一个普通字段，不转义html：   {{ d.field }}
 * 			输出一个普通字段，并转义html：   {{= d.field }}
 * 			JavaScript脚本： {{# JavaScript statement }}
 */
js.template(id, data, callback);

/////////////////////////////////////// ajax form

/**
 * AJAX 提交
 * js.ajaxSubmit('/sys/user/save', {param: 1}, function(data){})
 */
js.ajaxSubmit(url, data, callback, dataType, async, message);

/**
 * AJAX 提交表单（支持文件上传）
 * js.ajaxSubmitForm($(form), function(data){})
 */
js.ajaxSubmitForm(formJqueryObj, callback, dataType, async, message);

/////////////////////////////////////// string

/**
 * String两边去空格
 */
js.trim(str);

/**
 * String的startWith
 */
js.startWith(str, start);

/**
 * String的endWith
 */
js.endWith(str, end);

/**
 * 截取字符串，区别汉字和英文
 */
js.abbr(name, maxLength);

/////////////////////////////////////// number

/**
 * 格式化数值
 * @param num 待格式化的树
 * @param cent 保留小数位数
 * @param isThousand 是否进行千分位格式化
 */
js.formatNumber(num, cent, isThousand);

/**
 * 金额格式化（千位符，小数四舍五入）金额每隔三位加一个逗号
 * @param s 要格式化的数值
 * @param n 小数位数
 */
js.formatMoney(s, n);

/**
 * 数值前补零
 */
js.numberPad(num, n);

/////////////////////////////////////// date

/**
 * 日期格式化
 * @param date 日期 new Date()
 * @param f 格式化字符串 yyyy-MM-dd HH:mm:ss
 */
js.formatDate(date, f);

/**
 * 字符串转为日期
 * @param date
 */
js.parseDate(date);

/**
 * 日期加减
 * @param date
 * @param dadd 天数
 */
js.addDate(date, dadd);

/**
 * 快速选择日期方法
 * @param type 1今日，2本周，3本月，4本季度，5上月
 * @param beginDateId 开始日期控件的ID
 * @param endDateId	 结束日期控件的ID
 */
js.quickSelectDate(type, beginDateId, endDateId);

/////////////////////////////////////// cookie

/**
 * cookie 操作
 * @param name Cookie名称
 * @param value Cookie值，填写表示设置，不填写表示获取
 * @parma options:{expires:7} 如果是数字，则expires单位为天。
 */
js.cookie(name, value, options);

/////////////////////////////////////// tabPage

/**
 * 得到TabPage对象
 */
js.tabPage;

/**
 * 初始化TAB页面
 * @param id
 */
js.initTabPage(id, options);

/**
 * 添加TAB页面
 * @param $this 		点击的对象
 * @param title 		提示标题
 * @param url	 		访问的路径
 * @param closeable	 	是否有关闭按钮
 * @param refresh 		打开后是否刷新重新加载
 */
js.addTabPage($this, title, url, closeable, refresh);

/**
 * 获取当前TAB页面
 * @param currentTabCallback(contents, contentWindow) 当前页面回调方法，传入当前tab页面的contents和contentWindow
 */
js.getCurrentTabPage(currentTabCallback);

/**
 * 获取当前页面的上一个TAB页面，并激活上级页面
 * @param preTabCallback(contents, contentWindow) 传入上一个tab页面的contents和contentWindow
 * @param isCloseCurrentTab 是否关闭当前页签
 */
js.getPrevTabPage(preTabCallback, isCloseCurrentTab);

/**
 * 关闭当前TAB页面，并激活上级页面
 * @param preTabCallback(contents, contentWindow) 关闭前的回调方法，传入上一个tab页面的contents和contentWindow
 */
js.closeCurrentTabPage(preTabCallback);
</code></pre>
<h2>封装数据表格组件DataGrid</h2>
<p>数据表格是一个必不可少的元素，在选择这个选型的时候尝试了很多开源组件，最终选择jqGrid，只是因为它接近经典思维，用着还算顺手，最主要的是遇见什么问题都可以自行解决和修复问题，有人说jqGrid不好看，这没关系这完全而已自行编写CSS改造它，下面看看一个简单的例子：</p> 
<pre><code>&lt;#form:form id="searchForm" model="${config}" action="${ctx}/sys/config/listData" method="post" class="form-inline "
		data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}"&gt;
	参数名称：&lt;#form:input path="configName" maxlength="100" class="form-control" /&gt;
	参数键名：&lt;#form:input path="configKey_like" maxlength="100" class="form-control" /&gt;
	&lt;button type="submit" class="btn btn-primary btn-sm"&gt;查询&lt;/button&gt;
	&lt;button type="reset" class="btn btn-default btn-sm"&gt;重置&lt;/button&gt;
&lt;/#form:form&gt;
&lt;table id="dataGrid"&gt;&lt;/table&gt;
&lt;div id="dataGridPage"&gt;&lt;/div&gt;
</code></pre> 
<pre><code>// 初始化DataGrid对象
$('#dataGrid').dataGrid({
    // 查询数据表单
    searchForm: $('#searchForm'),
	// 设置数据表格列
	columnModel: [ 
		{header:'参数名称', name:'configName', index:'a.config_name', width:200, formatter: function(val, obj, row, act){
			return '&lt;a href="${ctx}/sys/config/form?id='+row.id+'" class="btnList" data-title="编辑参数"&gt;'+val+'&lt;/a&gt;';
		}},
		{header:'参数键名', name:'configKey', index:'a.config_key', width:200},
		{header:'参数键值', name:'configValue', sortable:false, width:260, classes:"nowrap"},
		{header:'操作', name:'actions', width:100, sortable:false, title:false, formatter: function(val, obj, row, act){
			var actions = [];
			&lt;% if(hasPermi('sys:config:edit')){ %&gt;
				actions.push('&lt;a href="${ctx}/sys/config/form?id='+row.id+'" class="btnList" title="编辑参数"&gt;&lt;i class="fa fa-pencil"&gt;&lt;/i&gt;&lt;/a&gt;&nbsp;');
			&lt;% } %&gt;
			&lt;% if(hasPermi('sys:config:delete')){ %&gt;
				actions.push('&lt;a href="${ctx}/sys/config/delete?id='+row.id+'" class="btnList" title="删除参数" data-confirm="确认要删除该参数吗？"&gt;&lt;i class="fa fa-trash-o"&gt;&lt;/i&gt;&lt;/a&gt;&nbsp;');
			&lt;% } %&gt;
			return actions.join('');
		}}
	],
	// 加载成功后执行事件
	ajaxSuccess: function(data){
		
	}
});
</code></pre>
<p>是不是比你使用foreach方便的多，封装后名字叫dataGrid，这只是展示了冰山一角，它支持所有jqGrid参数，即简化了代码编写，又不失功能</p>
<h2>提供丰富的演示例子</h2> 
<ul> 
 <li>AdminLTE 2.4</li> 
 <li>Bootstrap 3.3</li> 
 <li>Layer 3.0</li> 
 <li>My97DatePicker 4.8</li> 
 <li>jQurey Select2 4.0</li> 
 <li>jQurey Validation 1.16</li> 
 <li>jQurey zTree API 3.5</li> 
 <li>jQurey zTree Demo 3.5</li> 
 <li>jQuery jqGrid 4.7</li> 
</ul>
			</div>

																														<h2 id="category_54969">开发技巧</h2>
																								<h3 id="text_267355" class="sin_target">替换默认视图，自定义主题</h3>
<div class="sin_text">
    					<p>每个项目的需求都不同，风格要求也不同，例如我想修改下登录页、框架页面、错误页面等待，那在JeeSite4.0中如何覆盖默认视图视图呢？</p>
<p>1、先了解下SpringMVC的视图配置：</p> 
<pre><code>// Beetl主题视图解析器（order越小优先级越高）
BeetlViewResolver beetlThemes = new BeetlViewResolver();
beetlThemes.setPrefix("/themes/" + Global.getProperty("web.view.themeName") + "/");
beetlThemes.setSuffix(".html");
beetlThemes.setOrder(10000);
registry.viewResolver(beetlThemes);
// Beetl默认视图解析器（order越小优先级越高）
BeetlViewResolver beetlDefault = new BeetlViewResolver();
beetlDefault.setPrefix("/");
beetlDefault.setSuffix(".html");
beetlDefault.setOrder(20000);
registry.viewResolver(beetlDefault);
// 默认视图定义，根据后缀渲染（.json、.xml）
JsonView jsonView = new JsonView();
jsonView.setPrettyPrint(false);
XmlView xmlView = new XmlView();
xmlView.setPrettyPrint(false);
registry.enableContentNegotiation(jsonView, xmlView);
</code></pre>
<p>2、我们再看下<code>jeesite.yml</code>里视图相关配置<code>themeName</code>默认为<code>default</code>。</p> 
<pre><code># Web 相关
web:
  
  # MVC 视图相关
  view:
    
    # 系统主题名称，主题视图优先级最高，如果主题下无这个视图文件则访问默认视图
    # 引入页面头部：'/themes/'+themeName+'/include/header.html'
    # 引入页面尾部：'/themes/'+themeName+'/include/footer.html'
    themeName: default
</code></pre>
<p>3、有经验的小伙伴也许一看就明白，MVC的视图加载机制，原来是依照谁先加载谁受用原则进行的，也就是说优先级越高的视图被找到就用谁，后面的视图将会被忽略。顺序如下：<code>*/src/main/resources/views/themes/default/**/*.html</code> –&gt; <code>*/src/main/resources/views/**/*.html</code> –&gt; <code>JSON/XML</code>，先去找主题目录下的视图文件，找不到使用JeeSite默认，如果还找不到，则返回JSON或XML数据。</p>
<p>4、下面我们来举个例子（修改登录页）</p> 
<ul> 
 <li><p>复制 <code>/jeesite-module-core/src/main/resources/views/modules/sys/sysLogin.html</code> 文件到 <code>/web/src/main/resources/views/themes/default/modules/sys/sysLogin.html</code>目录下。</p></li> 
 <li><p>然后修改，刚刚复制的<code>sysLogin.html</code>视图文件内容，改成你满意的样式，这样就大功告成啦。</p></li> 
</ul>
<p>5、如果你想在公共的地方加载自己的css或js，JeeSite为你提供了两个公共文件，<code>/src/main/webapp/static/common/common.css</code>和<code>/src/main/webapp/static/common/common.js</code>文件，你可以修改它。</p>
<h1>自定义主题，快速切换视图</h1>
<p>1、JeeSite 4.0 版本已提供快速换肤功能，这里的换肤不是单单的换掉css改变下配色，而是，把系统中的所有的视图，包括登录页、框架页、列表页、内容页，甚至换掉grid组件。</p>
<p>2、上一章中说到了<code>jeesite.yml</code>里视图相关配置<code>themeName</code>，其实上一章节的思路是让你修改和完善<code>default</code>这个主题，如果你想大范围的修改完全不同的UI，建议你再起一个名字。我们可以吧这个改为你想起的视图名字，如：<code>good</code>，这样你的所有视图文件，资源文件将都在这个目录下：<code>/src/main/resources/views/themes/good/</code>、<code>/src/main/resources/static/themes/good/</code>。另外还有两个目录，公共include的头部和尾部：<code>/themes/good/include/header.html</code>、<code>/themes/good/include/footer.html</code>。</p>
<p>3、下面如何操作就不用就不赘述了，和上一章节例子相同。</p>
			</div>

																						<h3 id="text_270527" class="sin_target">前后分离、接口调用、移动端API</h3>
<div class="sin_text">
    					<p>有不少朋友咨询我，想用JeeSite做前后分开的应用，或者手机端API怎么调用？又或者只想用JeeSite作为服务端API，仅提供服务接口怎么做？那这篇文章一定适合你，下面我来介绍下一些JeeSite已内置的接口及如果自己来开发API接口提供服务。</p>
<h1>内置接口</h1>
<h2>系统登录</h2>
<p>JeeSite的系统默认登录，设置了DES加密，如果不想加密，可将secretKey设置为空即可，或更改密钥，配置如下（jeesite.yml）：</p> 
<pre><code>shiro:
  loginSubmit:
    # 登录提交信息安全Key，加密用户名、密码、验证码，后再提交（key设置为3个，用逗号分隔）
    secretKey: thinkgem,jeesite,com
    # 设置为空，关闭登录DES加密。
    # secretKey: ~

  # 如果是JS请求可能会有跨域访问问题，可将如下参数设置为，允许的域名，全部域名设置*号，否则设置为空
  accessControlAllowOrigin: '*'
</code></pre>
<p>如果开启了加密，你就需要先将DES加密工具引入：</p>
<p>JS：<code>&lt;script src="${ctxStatic}/common/des.js?${_version}"&gt;&lt;/script&gt;</code></p>
<p>Java: <code>com.jeesite.common.codec.DesUtils</code></p>
<p>引入完成之后就可以通过如下方法进行调用加密了：</p>
<p>JS：<br> </p>
<pre><code><br>&lt;script&gt;
var secretKey = '${@Global.getConfig("shiro.loginSubmit.secretKey")}';
var username = DesUtils.encode('system', secretKey);
var password = DesUtils.encode('admin', secretKey);
console.log('&amp;username=' + username + '&amp;password=' + password);
&lt;/script&gt;
</code></pre>
<p></p>
<p>Java：<br> </p>
<pre><code><br>String secretKey = Global.getConfig("shiro.loginSubmit.secretKey");
String username = DesUtils.encode("system", secretKey);
String password = DesUtils.encode("admin", secretKey);
System.out.println("&amp;username=" + username + "&amp;password=" + password);
</code></pre>
<p></p>
<p>以上两种语言，输出结果相同如下：<br> </p>
<pre><code><br>&amp;username=F3EDC7D2C193E0B8DCF554C726719ED2&amp;password=235880C505ACCDA5C581A4F4CDB81DA0
</code></pre>
<p></p>
<p>下面我们就可以拿着这个用户名密码进行测试登录了。</p>
<p>通过JS的Ajax或者通过Java的HttpClient进行POST或GET请求如下地址，即可进行登录验证：</p> 
<pre><code>http://127.0.0.1:8980/js/a/login?__login=true&amp;__ajax=json&amp;username=F3EDC7D2C193E0B8DCF554C726719ED2&amp;password=235880C505ACCDA5C581A4F4CDB81DA0
</code></pre>
<p>你也可以添加登录附加参数如下：<br> </p>
<pre><code><br>1、可以指定登录设备类型（在线用户列表区分、登录验证码按设备区分，可根据设备指定session超时时间，默认PC）：
   &amp;param_deviceType=mobileApp
2、可以指定登录的系统（区分不同的菜单，默认default）
   &amp;param_sysCode=mobileApp
3、可以指定登录页面和主框架页的视图（默认：employee）
   &amp;param_userType=member
</code></pre>
<p></p>
<p>若登录信息不正确，则返回如下失败JSON数据：<br> </p>
<pre><code><br>{
	"username": "F3EDC7D2C193E110B8DCF554C726719ED2",
	"rememberMe": false,
	"rememberUserCode": false,
	"params": "",
	"shiroLoginFailure": "org.apache.shiro.authc.UnknownAccountException",
	"message": "账号或密码错误, 请重试.",
	"isValidCodeLogin": false,
	"result": "false",
	"sessionid":"2a6669501bf24afebcf4ff63eb048a56"
}
</code></pre>
<p></p>
<p>如果失败，第二次登录，建议附加一个__sid参数，用来指明是同一个会话，如：<br> </p>
<pre><code><br>http://127.0.0.1:8980/js/a/login?__login=true&amp;__ajax=json&amp;username=F3EDC7D2C193E0B8DCF554C726719ED2&amp;password=235880C505ACCDA5C581A4F4CDB81DA0&amp;__sid=2a6669501bf24afebcf4ff63eb048a56
</code></pre>
<p></p>
<p>注意：若参数配置的密码失败次数超过了预警值，则返回的结果信息中的<code>isValidCodeLogin</code>会变为<code>true</code>，这时你需要调用<code>http://127.0.0.1:8980/js/validCode?__sid=2a6669501bf24afebcf4ff63eb048a56</code>地址来获取验证码图片，另外请注意，移动端一般调用是无cookie的，建议加请求参数中要包含__sid参数，否则获取到的验证码值将无法与您登录请求会话匹配。</p>
<p>若登录信息正确，则返回如下登录成功JSON数据：<br> </p>
<pre><code><br>{
	"user": {
		"id": "system",
		"status": "0",
		"remarks": "",
		"userCode": "system",
		"loginCode": "system",
		"userName": "超级管理员",
		"userType": "none",
		"mgrType": "0",
		"lastLoginIp": "127.0.0.1",
		"lastLoginDate": "2018-03-14 22:34:44",
		"userWeight": 0,
		"oldLastLoginIp": "127.0.0.1",
		"corpName_": "JeeSite",
		"corpCode_": "0",
		"oldLoginDate": "2018-03-14 22:34:44",
		"avatarUrl": "/ctxPath/static/images/user1.jpg"
	},
	"result": "true",
	"message": "登录成功！",
	"sessionid": "5fe9c7c45ded4425b03eff8f78179637"
}
</code></pre>
<p></p>
<p>在登录成功的信息里，也有个 sessionid 属性，该属性值将作为你以后访问系统的凭证，相当于token令牌，举例如下：<br> </p>
<pre><code><br>1、获取用户权限信息：
  http://127.0.0.1:8980/js/a/authInfo?__sid=5fe9c7c45ded4425b03eff8f78179637
2、获取用户菜单信息：
  http://127.0.0.1:8980/js/a/menuTree?__sid=5fe9c7c45ded4425b03eff8f78179637
3、重新获取登录信息：
  http://127.0.0.1:8980/js/a/index.json?__sid=5fe9c7c45ded4425b03eff8f78179637
4、获取当前用户信息：
  http://127.0.0.1:8980/js/a/sys/user/info.json?__sid=5fe9c7c45ded4425b03eff8f78179637
</code></pre>
<p></p>
<h2>系统退出</h2> 
<pre><code>http://127.0.0.1:8980/js/a/logout?__ajax=json&amp;__sid=5fe9c7c45ded4425b03eff8f78179637
</code></pre>
<p>注意：无cookie环境下，必须要指定要退出的sessionid</p>
<p>返回JSON数据：<br> </p>
<pre><code><br>{"result":"true","message":"退出成功！"}
</code></pre>
<p></p>
<h1>接口发现</h1>
<p>剩下的接口就不一一说明了，交给大家一个接口发现的方法。</p>
<p>所有连接加 .json 或 .xml 或增加 <code>__ajax=json</code> 参数，或增加 <code>__ajax=xml</code> 参数，则自动返回 json 或 xml 数据，而不返回视图，举例：</p>
<p>用户列表的访问地址是 /a/sys/empUser/list，如果直接访问，则返回页面的视图界面，如果加后缀 .json，则返回视图所需要的json数据，如：/a/sys/empUser/list.json，这样返回的数据，就可以在你的前端分离应用中使用了。</p>
<p>所有列表加载的数据均使用 listData 为后缀获取数据，如用户列表的数据地址为 /a/sys/empUser/listData，则直接返回JSON数据。</p>
<p>listData只是一个命名规则，如果你发现了不遵循规范的地址，怎么办？你可以通过Chrome浏览器的开发者界面(F12)，打开Network，Filter中选择XHR，好了，准备就绪，这是你点击列表里的查询按钮，即可监控到访问的数据的地址是什么</p>
<h1>开发一个API接口</h1>
<p>在你的Controller映射方法上增加@ResponseBody即可返回JSON数据，而不返回视图</p>
<p>或者替换@Controller为@RestController，则应用所有映射方法均返回JSON数据。</p>
<p>如果你想两用，就如 接口发现 章节所述，增加 .json 后缀即可。</p>
<p>另外，对于移动端或高并发的应用对于流量是非常珍贵的，通过通用方法可能会返回一些很多无用的数据，这时，你最好单独覆写Rest清理无用的数据为null，则不会返回到前台。</p>
			</div>

																														<h2 id="category_64919">服务支持</h2>
																								<h3 id="text_267685" class="sin_target">技术服务与支持</h3>
<div class="sin_text">
    					<h1>服务条款</h1> 
<ol> 
 <li>不得应用于危害国家安全、荣誉和利益的行为，不能以任何形式用于非法为目的的行为。</li> 
 <li>付费之前请确认已通过开源版了解和试用“产品”，并确认产品功能符合您的需求；付费之后源代码与开源版本相同，不另外提供源代码，您只需上传许可文件即可；许可文件一经发出概不提供退货服务。</li> 
 <li>您必须了解使用本软件的风险，本软件无法保证100%没有漏洞，所以由软件漏洞造成的损失不予赔偿，也不承担任何因使用本软件而产生相关法律责任。请软件上线使用前进行足够的安全测试，以避免此问题发生。</li> 
 <li>若您已经购买本产品许可文件或以其它方式获得的许可文件，将被视为您对本服务条款全部的完全接受，如果您未能遵守本服务条款，您的许可授权将被终止，许可的权利将被收回，同时您应承担相应法律责任。</li> 
 <li>JeeSite官方对以上条款有最终的解释权。</li> 
</ol>
<h1>产品版本</h1>
<h2>版本区别一览表：</h2> 
<table>  
 <tbody><tr> 
  <th>服务/功能 </th> 
  <th>免费版 </th> 
  <th>个人版 </th> 
  <th>专业版</th> 
 </tr>  
 </tbody><tbody> 
  <tr> 
   <td>即时沟通群 </td> 
   <td>官方交流群 </td> 
   <td>官方交流群 </td> 
   <td>专享讨论组</td> 
  </tr> 
  <tr> 
   <td>VIP服务期限 </td> 
   <td>无 </td> 
   <td>无 </td> 
   <td>3年</td> 
  </tr> 
  <tr> 
   <td>远程桌面协助 </td> 
   <td>无 </td> 
   <td>无 </td> 
   <td>有</td> 
  </tr> 
  <tr> 
   <td>技术方案指导 </td> 
   <td>无 </td> 
   <td>有 </td> 
   <td>有</td> 
  </tr> 
  <tr> 
   <td>系统用户数量 </td> 
   <td>&lt;= 100个 </td> 
   <td>无限制 </td> 
   <td>无限制</td> 
  </tr> 
  <tr> 
   <td>最大在线人数 </td> 
   <td>&lt;= 10个(不含匿名) </td> 
   <td>无限制 </td> 
   <td>无限制</td> 
  </tr> 
  <tr> 
   <td>需求响应 </td> 
   <td>正常排队 </td> 
   <td>排队优先 </td> 
   <td>优先开发</td> 
  </tr> 
  <tr> 
   <td>平台搭建 </td> 
   <td>自行搭建 </td> 
   <td>指导搭建 </td> 
   <td>协助搭建</td> 
  </tr> 
  <tr> 
   <td>平台升级 </td> 
   <td>自行升级 </td> 
   <td>指导升级 </td> 
   <td>协助升级</td> 
  </tr> 
  <tr> 
   <td>程序代码 </td> 
   <td>码云 </td> 
   <td>码云 </td> 
   <td>码云、可深度合作</td> 
  </tr> 
  <tr> 
   <td>商业使用 </td> 
   <td>可以商用 </td> 
   <td>可以商用 </td> 
   <td>可以商用</td> 
  </tr> 
  <tr> 
   <td>安全性相关设置 </td> 
   <td>√ </td> 
   <td>√ </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>自定义视图皮肤 </td> 
   <td>√ </td> 
   <td>√ </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>多数据源分布式事务 </td> 
   <td>√ </td> 
   <td>√ </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>在线作业调度 </td> 
   <td>× </td> 
   <td>√ </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>CAS单点登录 </td> 
   <td>× </td> 
   <td>√ </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>Session集群 </td> 
   <td>× </td> 
   <td>× </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>SAAS多租户 </td> 
   <td>× </td> 
   <td>× </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>多语言国际化 </td> 
   <td>× </td> 
   <td>× </td> 
   <td>√</td> 
  </tr> 
  <tr> 
   <td>消息提醒中心 </td> 
   <td>× </td> 
   <td>× </td> 
   <td>开发中</td> 
  </tr> 
  <tr> 
   <td>许可文件有效时间 </td> 
   <td>无需许可 </td> 
   <td>永久有效 </td> 
   <td>永久有效</td> 
  </tr> 
  <tr> 
   <td>许可文件单价(不含发票) </td> 
   <td>￥0元 </td> 
   <td>￥1,000元 </td> 
   <td>×</td> 
  </tr> 
  <tr> 
   <td>优惠套餐价格(不含发票) </td> 
   <td>￥0元 </td> 
   <td>× </td> 
   <td>5个许可/￥20,000元</td> 
  </tr> 
 </tbody> 
</table>
<p><strong>优惠活动：</strong> 2018年5月1日之前购买专业版服务，享 9 折优惠，早购买早享受服务。</p>
<p><strong>由于团队精力有限，为了更好的服务于专业版客户，暂时取消个人版的VIP服务，并将单价从￥3,000元调整为￥1,000元，已购客户不受影响，敬请理解，感谢您的支持。</strong></p>
<p><strong>个性化定制，产品性质的项目，或者其它方案，价格商议。</strong></p>
<h2>许可文件使用须知：</h2> 
<ol> 
 <li>每个许可文件，只能应用于1个项目的1台服务器上部署，集群部署按1个项目付费，授权访问域名或IP可设置2个。</li> 
 <li>许可文件的注册信息可通过 “系统管理 -&gt; 系统设置 -&gt; 产品许可信息” 菜单查看，或通过此界面上传许可文件。</li> 
 <li>机器码有系统自动生成，产品名称和公司名称，请在 jeesite.yml 里进行配置 productName 和 companyName 值，必须是真实的产品名称和公司名称，尽量写全称，如果是 “个人” 公司名称可为真实姓名。</li> 
 <li>访问域名或IP，是您浏览器访问项目或产品的地址，这个域名可指定两个，例如：一个本地局域网访问的，一个是外网访问的。</li> 
 <li>如果您更换了部署服务器，您的机器码会有变化，您只需提供原许可文件信息和新的机器码，即可免费换领，但这里只能更换机器码或者只能更换域名IP，产品名称和公司名称不能更改。</li> 
</ol>
<h1>联系方式</h1>
<p>QQ：78112665</p>
<p><strong>请认准唯一收款码账号</strong>：thinkgem（王震）</p>
<p><img src="https://static.oschina.net/uploads/img/201803/16112020_sFWX.jpg" alt="ThinkGem的支付宝收款二维码" title="ThinkGem的支付宝收款二维码"></p>
<p><img src="https://static.oschina.net/uploads/img/201803/12160758_glou.jpg" alt="ThinkGem的支付宝收款二维码" title="ThinkGem的支付宝收款二维码"></p>
<h1>捐赠答谢</h1>
<p>感谢曾经捐赠支持过我们的朋友们，截止2018年3月15号之前，只要您给我们捐赠过，不管金额多少，都是对我们最大的支持，我们将免费赠送给您1个专业版许可；捐献100元以上赠送2个专业版许可；捐赠200元以上赠送3个专业版许可；捐赠500元以上赠送5个专业版许可。</p>
<p>捐赠记录已导出，领取方法：加 QQ78112665 并发送以下内容到邮箱　thinkgem@163.com，提供：捐赠平台类型（支付宝、微信、码云），捐赠平台ID，捐赠者姓名，捐赠时间（精确到分钟）；领取的许可信息：机器码、产品名称、公司名称、产品访问的域名或IP地址；领取人联系方式：真实姓名、QQ、邮箱、电话；不可重复领取。</p>
<p><strong>注意：</strong>机器码是启动服务时打印Banner的机器码；产品名称是真实的项目名称，在jeesite.yml里进行配置的productName值；如果是个人公司名称写真实姓名就可以；访问域名或ip是您浏览器访问项目地址中的域名或ip地址。</p>
<h1>关注赠送活动</h1> 
<ul> 
 <li>Gitee：<a href="https://gitee.com/thinkgem/jeesite4" rel="nofollow">https://gitee.com/thinkgem/jeesite4</a></li> 
 <li>GitHub：<a href="https://github.com/thinkgem/jeesite4" rel="nofollow">https://github.com/thinkgem/jeesite4</a></li> 
 <li>微信公众号：</li> 
</ul>
<p><img src="https://static.oschina.net/uploads/space/2018/0302/145133_OGZf_941661.jpg" alt="https://static.oschina.net/uploads/space/2018/0302/145133_OGZf_941661.jpg" title="JeeSite4微信公众号"></p>
<p><strong>以上 Git 开源地址进行 Start 和 Fork 并且关注 JeeSite4 微信公众号，即可免费领取 1个 个人版 许可。</strong></p>
<h2>申请方式：</h2>
<p><strong>活动有效期: 2018年5月1日之前</strong></p> 
<ol> 
 <li>Gitee和GitHub 的 Start 和 Fork 截图 4 张、Gitee和GitHub 的账号 ID</li> 
 <li>关注 JeeSite4 的微信公众号的截图1张、微信昵称、微信账号</li> 
 <li>申请的许可信息：机器码、产品名称、公司名称、产品访问的域名或IP地址</li> 
 <li>申请人联系方式：真实姓名、QQ、邮箱、电话</li> 
 <li>加　QQ78112665　并发送以上内容到邮箱　thinkgem@163.com</li> 
 <li>每个Gitee和GitHub账号和微信只能领取1次</li> 
</ol>
<p><strong>注意：</strong>机器码是启动服务时打印Banner的机器码；产品名称是真实的项目名称，在jeesite.yml里进行配置的productName值；如果是个人公司名称写真实姓名就可以；访问域名或ip是您浏览器访问项目地址中的域名或ip地址。</p>
			</div>

																									
				  		<div class="sin_info" style="margin-top: 10px;">
	  			https://my.oschina.net/thinkgem/blog
	  		</div>
	  				</div>
		<div class="clear"></div>
	</div>
	<div id="bottom_begin"></div>
	<div class="sin_footer">
		<div>
			Powered by <a href="http://www.oschina.net" target="_blank">开源中国</a>
		</div>
	</div>
	<script>
		//计算图片的真实大小，如果超过编辑区域，则进行限制
		var resizePicWidth = function(pic_width){
			var imgs = document.getElementsByTagName("img");
            var j=0;
			for(var i=0;i<imgs.length;i++){
                var realWidth;	//真实的宽度
                var realHeight;		//真实的高度
                //这里做下说明，$("<img/>")这里是创建一个临时的img标签，类似js创建一个new Image()对象！
				var newImg = new Image();
				newImg.onload=function() {
                    realWidth = imgs[j].width;
                    realHeight = imgs[j].height;
                    //如果真实的宽度大于规定的宽度就按照100%显示
                    if(realWidth>=pic_width){
						imgs[j].style.width=(pic_width) + "px";
                    } else{//如果小于浏览器的宽度按照原尺寸显示
						imgs[j].style.width=realWidth+'px';
                    }
                    j++;
                }
				newImg.src=imgs[j].src;
			}

		}
		var currentNode = null;
		window.onscroll=function(){
            var h = document.getElementById("top_end").getBoundingClientRect().top;
            if(h<=0){
				document.getElementById("sin_navi_id").className='sin_navi sin_navi_nomove';
				document.getElementById("sin_content_id").className='sin_content sin_content_mar';
            }else{
                document.getElementById("sin_navi_id").className='sin_navi';
                document.getElementById("sin_content_id").className='sin_content';
            }
            comLayout();
            var arr= new Array();
            var index = 0;
			var sinTargets = getClass("h2","sin_target");
			for(var i=0;i<sinTargets.length;i++){
                var th = sinTargets[i].getBoundingClientRect().top - 80;	                if(th<=0){
                    arr[index] = new Array();
                    arr[index][0] = sinTargets[i].getAttribute("id");
                    arr[index][1] = th;
                    index++;
                }
			}

            var curr = bubbleSort(arr);
            if(curr!=null && curr!=currentNode){
				var oldCurrIds = getClass("li","sin_navi_current");
                if(oldCurrIds && oldCurrIds[0]){
                    var oid=oldCurrIds[0].getAttribute("id");
                    document.getElementById(oid).className=" ";
                }
				document.getElementById("navi_"+curr).className="sin_navi_current";
                currentNode = curr;
            }
        }
		
		
	    window.onresize = function(){
            comLayout();
		}

        var ch = document.getElementById("sin_navi_id").offsetHeight;

	    function comLayout() {
	        var h = document.documentElement.clientHeight;
	    	if(ch<h){
	    		return;
	    	}
		    var i = document.getElementById("bottom_begin").getBoundingClientRect().top;
	        if(i<h){
                document.getElementById("sin_navi_id").style.height=(i+"px");
		   	}else{
                document.getElementById("sin_navi_id").style.height=(h+"px");
		   	}
	    }
	    
	    function bubbleSort(arr){
	    	var i= arr.length;
	    	if(i<=0){
	    		return null;
	    	}
	    	var j;
			var tempExchangVal;
			while(i>0){
				for(j=0;j<i-1;j++){
					if(arr[j][1] < arr[j+1][1]){
						tempExchangVal=arr[j];
						arr[j]=arr[j+1];
						arr[j+1]=tempExchangVal;
					}
				}
				i--;
			}
			return arr[0][0];
		}
	    comLayout();
	    resizePicWidth(680);

        function getClass(tagname, className) { //tagname指元素，className指class的值
            //判断浏览器是否支持getElementsByClassName，如果支持就直接的用
            if (document.getElementsByClassName) {
                return document.getElementsByClassName(className);
            }
            else {    //当浏览器不支持getElementsByClassName的时候用下面的方法
                var tagname = document.getElementsByTagName(tagname);  //获取指定元素
                var tagnameAll = [];     //这个数组用于存储所有符合条件的元素
                for (var i = 0; i < tagname.length; i++) {     //遍历获得的元素
                    if (tagname[i].className == className) {     //如果获得的元素中的class的值等于指定的类名，就赋值给tagnameAll
                        tagnameAll[tagnameAll.length] = tagname[i];
                    }
                }
                return tagnameAll;
            }
        }
	</script>
</body>
</html>	
<!-- Generated by OsChina.NET (init:0[ms],page:168[ms],ip:39.188.251.37) -->